Detect and correct for client side clock skew

This commit is contained in:
Zaid Abdulla committed 2021-07-25 12:48:42 -07:00
1 parent cfe279b511
commit 1a53a320da
2 files changed
+37 -8

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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(