1 | dojo.provide("dojox.cometd.timesync"); |
---|
2 | dojo.require("dojox.cometd._base"); |
---|
3 | |
---|
4 | /** |
---|
5 | * this file provides the time synchronization extension to cometd. |
---|
6 | * Timesync allows the client and server to exchange time information on every |
---|
7 | * handshake and connect message so that the client may calculate an approximate |
---|
8 | * offset from it's own clock epoch to that of the server. |
---|
9 | * |
---|
10 | * With each handshake or connect, the extension sends timestamps within the |
---|
11 | * ext field like: <code>{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}</code> |
---|
12 | * where:<ul> |
---|
13 | * <li>tc is the client timestamp in ms since 1970 of when the message was sent. |
---|
14 | * <li>l is the network lag that the client has calculated. |
---|
15 | * <li>o is the clock offset that the client has calculated. |
---|
16 | * </ul> |
---|
17 | * The accuracy of the offset and lag may be calculated with tc-now-l-o, |
---|
18 | * which should be zero if the calculated offset and lag are perfectly |
---|
19 | * accurate. |
---|
20 | * <p> |
---|
21 | * A cometd server that supports timesync, should respond only if the |
---|
22 | * measured accuracy value is greater than accuracy target. The response |
---|
23 | * will be an ext field like: <code>{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}</code> |
---|
24 | * where:<ul> |
---|
25 | * <li>tc is the client timestamp of when the message was sent, |
---|
26 | * <li>ts is the server timestamp of when the message was received |
---|
27 | * <li>p is the poll duration in ms - ie the time the server took before sending the response. |
---|
28 | * <li>a is the measured accuracy of the calculated offset and lag sent by the client |
---|
29 | * </ul> |
---|
30 | * |
---|
31 | * On receipt of the response, the client is able to use current time to determine |
---|
32 | * the total trip time, from which p is subtracted to determine an approximate |
---|
33 | * two way network traversal time. The measured accuracy is used to adjust the assumption |
---|
34 | * that the network is symmetric for traversal time, so: <ul> |
---|
35 | * <li>lag = (now-tc-p)/2-a |
---|
36 | * <li>offset = ts-tc-lag |
---|
37 | * </ul> |
---|
38 | * |
---|
39 | * In order to smooth over any transient fluctuations, the extension keeps a sliding |
---|
40 | * average of the offsets received. By default this is over 10 messages, but this can |
---|
41 | * be changed with the dojox.cometd.timesync._window element. |
---|
42 | */ |
---|
43 | dojox.cometd.timesync = new function(){ |
---|
44 | this._window = 10; // The window size for the sliding average of offset samples. |
---|
45 | this._lags = []; // The samples used to calculate the average lag. |
---|
46 | this._offsets = []; // The samples used to calculate the average offset. |
---|
47 | this.lag=0; // The calculated network lag from client to server |
---|
48 | this.offset = 0; // The offset in ms between the clients clock and the servers clock. |
---|
49 | this.samples = 0; // The number of samples used to calculate the offset. If 0, the offset is not valid. |
---|
50 | |
---|
51 | this.getServerTime = function(){ // return: long |
---|
52 | // Summary: |
---|
53 | // Calculate the current time on the server |
---|
54 | // |
---|
55 | return new Date().getTime()+this.offset; |
---|
56 | } |
---|
57 | |
---|
58 | this.getServerDate = function(){ // return: Date |
---|
59 | // Summary: |
---|
60 | // Calculate the current time on the server |
---|
61 | // |
---|
62 | return new Date(this.getServerTime()); |
---|
63 | } |
---|
64 | |
---|
65 | this.setTimeout = function(/*function*/call, /*long|Date*/atTimeOrDate){ |
---|
66 | // Summary: |
---|
67 | // Set a timeout function relative to server time |
---|
68 | // call: |
---|
69 | // the function to call when the timeout occurs |
---|
70 | // atTimeOrTime: |
---|
71 | // a long timestamp or a Date representing the server time at |
---|
72 | // which the timeout should occur. |
---|
73 | |
---|
74 | var ts = (atTimeOrDate instanceof Date) ? atTimeOrDate.getTime() : (0 + atTimeOrDate); |
---|
75 | var tc = ts - this.offset; |
---|
76 | var interval = tc - new Date().getTime(); |
---|
77 | if(interval <= 0){ |
---|
78 | interval = 1; |
---|
79 | } |
---|
80 | return setTimeout(call,interval); |
---|
81 | } |
---|
82 | |
---|
83 | this._in = function(/*Object*/msg){ |
---|
84 | // Summary: |
---|
85 | // Handle incoming messages for the timesync extension. |
---|
86 | // description: |
---|
87 | // Look for ext:{timesync:{}} field and calculate offset if present. |
---|
88 | // msg: |
---|
89 | // The incoming bayeux message |
---|
90 | |
---|
91 | var channel = msg.channel; |
---|
92 | if(channel && channel.indexOf('/meta/') == 0){ |
---|
93 | if(msg.ext && msg.ext.timesync){ |
---|
94 | var sync = msg.ext.timesync; |
---|
95 | var now = new Date().getTime(); |
---|
96 | var l=(now-sync.tc-sync.p)/2-sync.a; |
---|
97 | var o=sync.ts-sync.tc-l; |
---|
98 | |
---|
99 | this._lags.push(l); |
---|
100 | this._offsets.push(o); |
---|
101 | if(this._offsets.length > this._window){ |
---|
102 | this._offsets.shift(); |
---|
103 | this._lags.shift(); |
---|
104 | } |
---|
105 | this.samples++; |
---|
106 | l=0; |
---|
107 | o=0; |
---|
108 | for(var i in this._offsets){ |
---|
109 | l+=this._lags[i]; |
---|
110 | o+=this._offsets[i]; |
---|
111 | } |
---|
112 | this.offset = parseInt((o / this._offsets.length).toFixed()); |
---|
113 | this.lag = parseInt((l / this._lags.length).toFixed()); |
---|
114 | |
---|
115 | } |
---|
116 | } |
---|
117 | return msg; |
---|
118 | } |
---|
119 | |
---|
120 | this._out = function(msg){ |
---|
121 | // Summary: |
---|
122 | // Handle outgoing messages for the timesync extension. |
---|
123 | // description: |
---|
124 | // Look for handshake and connect messages and add the ext:{timesync:{}} fields |
---|
125 | // msg: |
---|
126 | // The outgoing bayeux message |
---|
127 | |
---|
128 | var channel = msg.channel; |
---|
129 | if(channel && channel.indexOf('/meta/') == 0){ |
---|
130 | var now = new Date().getTime(); |
---|
131 | if(!msg.ext){ |
---|
132 | msg.ext = {}; |
---|
133 | } |
---|
134 | msg.ext.timesync = {tc:now,l:this.lag,o:this.offset}; |
---|
135 | } |
---|
136 | return msg; |
---|
137 | } |
---|
138 | }; |
---|
139 | |
---|
140 | dojox.cometd._extendInList.push(dojo.hitch(dojox.cometd.timesync, "_in")); |
---|
141 | dojox.cometd._extendOutList.push(dojo.hitch(dojox.cometd.timesync, "_out")); |
---|