Detect and correct for client side clock skew
This commit is contained in:
1 parent
cfe279b511
commit
1a53a320da
2 files changed
+37
-8
No files matched your search
@@ -78,7 +78,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
const { value, timestamp, parameter } = evaluateParam(
|
const { value, timestamp, parameter } = evaluateParam(
|
||||||
vnode.attrs["parameter"],
|
vnode.attrs["parameter"],
|
||||||
vnode.attrs["device"],
|
vnode.attrs["device"],
|
||||||
store.getTimestamp()
|
store.getTimestamp() + store.getClockSkew()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
@@ -117,7 +117,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
e.redraw = false;
|
e.redraw = false;
|
||||||
// Don't update any child element
|
// Don't update any child element
|
||||||
if (e.target === (el as VnodeDOM).dom) {
|
if (e.target === (el as VnodeDOM).dom) {
|
||||||
const now = Date.now();
|
const now = Date.now() + store.getClockSkew();
|
||||||
const localeString = new Date(timestamp).toLocaleString();
|
const localeString = new Date(timestamp).toLocaleString();
|
||||||
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
e.target.title = `${localeString} (${timeAgo(now - timestamp)})`;
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-6
@@ -34,6 +34,8 @@ const memoizedEvaluate = memoize(evaluate);
|
|||||||
let fulfillTimestamp = 0;
|
let fulfillTimestamp = 0;
|
||||||
let connectionNotification, configNotification, versionNotification;
|
let connectionNotification, configNotification, versionNotification;
|
||||||
|
|
||||||
|
let clockSkew = 0;
|
||||||
|
|
||||||
const queries = {
|
const queries = {
|
||||||
filter: new WeakMap(),
|
filter: new WeakMap(),
|
||||||
bookmark: new WeakMap(),
|
bookmark: new WeakMap(),
|
||||||
@@ -93,11 +95,13 @@ class QueryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkConnection(): void {
|
function checkConnection(): void {
|
||||||
|
const now1 = Date.now();
|
||||||
m.request({
|
m.request({
|
||||||
url: "status",
|
url: "status",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
background: true,
|
background: true,
|
||||||
extract: (xhr) => {
|
extract: (xhr) => {
|
||||||
|
const now2 = Date.now();
|
||||||
if (xhr.status !== 200) {
|
if (xhr.status !== 200) {
|
||||||
if (!connectionNotification) {
|
if (!connectionNotification) {
|
||||||
connectionNotification = notifications.push(
|
connectionNotification = notifications.push(
|
||||||
@@ -112,6 +116,21 @@ function checkConnection(): void {
|
|||||||
connectionNotification = null;
|
connectionNotification = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nowAvg = Math.trunc((now1 + now2) / 2);
|
||||||
|
const skew = Date.parse(xhr.getResponseHeader("Date")) - nowAvg;
|
||||||
|
if (Math.abs(skew - clockSkew) > 5000 && now2 - now1 < 1000) {
|
||||||
|
clockSkew = skew;
|
||||||
|
console.warn(
|
||||||
|
`System and server clocks are out of sync. Adding ${clockSkew}ms offset to any client-side time relative calculations.`
|
||||||
|
);
|
||||||
|
setTimestamp(now2);
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore in case of missing or invalid Date header
|
||||||
|
}
|
||||||
|
|
||||||
const configChanged =
|
const configChanged =
|
||||||
xhr.getResponseHeader("x-config-snapshot") !== configSnapshot;
|
xhr.getResponseHeader("x-config-snapshot") !== configSnapshot;
|
||||||
const versionChanged =
|
const versionChanged =
|
||||||
@@ -209,7 +228,7 @@ export async function xhrRequest(
|
|||||||
|
|
||||||
export function unpackExpression(exp: Expression): Expression {
|
export function unpackExpression(exp: Expression): Expression {
|
||||||
if (!Array.isArray(exp)) return exp;
|
if (!Array.isArray(exp)) return exp;
|
||||||
const e = memoizedEvaluate(exp, null, fulfillTimestamp);
|
const e = memoizedEvaluate(exp, null, fulfillTimestamp + clockSkew);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +314,7 @@ function compareFunction(sort: {
|
|||||||
function findMatches(resourceType, filter, sort, limit): any[] {
|
function findMatches(resourceType, filter, sort, limit): any[] {
|
||||||
let value = [];
|
let value = [];
|
||||||
for (const obj of resources[resourceType].objects.values())
|
for (const obj of resources[resourceType].objects.values())
|
||||||
if (evaluate(filter, obj, fulfillTimestamp)) value.push(obj);
|
if (evaluate(filter, obj, fulfillTimestamp + clockSkew)) value.push(obj);
|
||||||
|
|
||||||
value = value.sort(compareFunction(sort));
|
value = value.sort(compareFunction(sort));
|
||||||
if (limit) value = value.slice(0, limit);
|
if (limit) value = value.slice(0, limit);
|
||||||
@@ -461,7 +480,7 @@ export function fulfill(accessTimestamp: number): void {
|
|||||||
|
|
||||||
for (const queryResponse of toFetch) {
|
for (const queryResponse of toFetch) {
|
||||||
let filter = queries.filter.get(queryResponse);
|
let filter = queries.filter.get(queryResponse);
|
||||||
filter = memoizedEvaluate(filter, null, fulfillTimestamp);
|
filter = memoizedEvaluate(filter, null, fulfillTimestamp + clockSkew);
|
||||||
const bookmark = queries.bookmark.get(queryResponse);
|
const bookmark = queries.bookmark.get(queryResponse);
|
||||||
const sort = queries.sort.get(queryResponse);
|
const sort = queries.sort.get(queryResponse);
|
||||||
if (bookmark) filter = limitFilter(filter, sort, bookmark);
|
if (bookmark) filter = limitFilter(filter, sort, bookmark);
|
||||||
@@ -476,7 +495,11 @@ export function fulfill(accessTimestamp: number): void {
|
|||||||
if (!diff) {
|
if (!diff) {
|
||||||
for (const queryResponse of toFetch) {
|
for (const queryResponse of toFetch) {
|
||||||
let filter = queries.filter.get(queryResponse);
|
let filter = queries.filter.get(queryResponse);
|
||||||
filter = memoizedEvaluate(filter, null, fulfillTimestamp);
|
filter = memoizedEvaluate(
|
||||||
|
filter,
|
||||||
|
null,
|
||||||
|
fulfillTimestamp + clockSkew
|
||||||
|
);
|
||||||
const limit = queries.limit.get(queryResponse);
|
const limit = queries.limit.get(queryResponse);
|
||||||
const bookmark = queries.bookmark.get(queryResponse);
|
const bookmark = queries.bookmark.get(queryResponse);
|
||||||
const sort = queries.sort.get(queryResponse);
|
const sort = queries.sort.get(queryResponse);
|
||||||
@@ -521,7 +544,9 @@ export function fulfill(accessTimestamp: number): void {
|
|||||||
|
|
||||||
for (const d of deleted) {
|
for (const d of deleted) {
|
||||||
const obj = resources[resourceType].objects.get(d);
|
const obj = resources[resourceType].objects.get(d);
|
||||||
if (evaluate(combinedFilterDiff, obj, fulfillTimestamp))
|
if (
|
||||||
|
evaluate(combinedFilterDiff, obj, fulfillTimestamp + clockSkew)
|
||||||
|
)
|
||||||
resources[resourceType].objects.delete(d);
|
resources[resourceType].objects.delete(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,6 +588,10 @@ export function setTimestamp(t: number): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getClockSkew(): number {
|
||||||
|
return clockSkew;
|
||||||
|
}
|
||||||
|
|
||||||
export function postTasks(
|
export function postTasks(
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
tasks: QueueTask[]
|
tasks: QueueTask[]
|
||||||
@@ -664,7 +693,7 @@ export function evaluateExpression(
|
|||||||
obj: Record<string, unknown>
|
obj: Record<string, unknown>
|
||||||
): Expression {
|
): Expression {
|
||||||
if (!Array.isArray(exp)) return exp;
|
if (!Array.isArray(exp)) return exp;
|
||||||
return memoizedEvaluate(exp, obj, fulfillTimestamp);
|
return memoizedEvaluate(exp, obj, fulfillTimestamp + clockSkew);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changePassword(
|
export function changePassword(
|
||||||
|
|||||||
Reference in new issue
Block a user