-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
compute the offset between the frontend and the backend using an inte…
…rceptor. use it in the top bar countdown
- Loading branch information
Showing
14 changed files
with
172 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { HttpEvent, HttpEventType, HttpHandlerFn, HttpRequest } from '@angular/common/http'; | ||
import { inject } from '@angular/core'; | ||
import { Store } from '@ngrx/store'; | ||
import { Observable } from 'rxjs'; | ||
import { tap } from 'rxjs/operators'; | ||
import { fromTimeOffset } from '../store/time-offset'; | ||
import { isRequestToApi } from './interceptor_common'; | ||
|
||
/** | ||
* Interceptor which measures the time difference betweeen the client and the time indicated on server response, and sends it to the store | ||
*/ | ||
export function timeOffsetComputationInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> { | ||
const store = inject(Store); | ||
return next(req).pipe( | ||
tap(event => { | ||
const localTs = Date.now(); | ||
if (!isRequestToApi(req)) return; | ||
if (event.type !== HttpEventType.Response) return; | ||
const serverDateString = event.headers.get('Date'); | ||
if (!serverDateString) return; | ||
const serverTs = Date.parse(serverDateString); | ||
const offset = serverTs - localTs; | ||
store.dispatch(fromTimeOffset.interceptorActions.reportOffset({ offset })); | ||
}) | ||
); | ||
|
||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { fromTimeOffset } from './time-offset.store'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { createActionGroup, props } from '@ngrx/store'; | ||
|
||
export const interceptorActions = createActionGroup({ | ||
source: 'Time offset interceptor', | ||
events: { | ||
reportOffset: props<{ offset: number }>(), | ||
}, | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { createReducer, on } from '@ngrx/store'; | ||
import { State, initialState } from './time-offset.state'; | ||
import { interceptorActions } from './time-offset.actions'; | ||
|
||
export const reducer = createReducer( | ||
initialState, | ||
|
||
on(interceptorActions.reportOffset, | ||
({ latestOffsets }, { offset }): State => ({ | ||
latestOffsets: [ ...latestOffsets.slice(-4), offset ], // only keep the latest 5 values at most | ||
}) | ||
), | ||
|
||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { MemoizedSelector, Selector, createSelector } from '@ngrx/store'; | ||
import { State } from './time-offset.state'; | ||
import { RootState } from 'src/app/utils/store/root_state'; | ||
import { median } from 'src/app/utils/array'; | ||
|
||
interface TimeOffsetSelectors<T extends RootState> { | ||
/** | ||
* The current time offset computed as the median of the 5 latest measured values. | ||
* A positive value means the client is late on the server time. So you have to add the offset to the client time to get the server one. | ||
*/ | ||
selectCurrentTimeOffset: MemoizedSelector<T, number>, | ||
} | ||
|
||
export function selectors<T extends RootState>(selectTimeOffsetState: Selector<T, State>): TimeOffsetSelectors<T> { | ||
return { | ||
selectCurrentTimeOffset: createSelector( | ||
selectTimeOffsetState, | ||
({ latestOffsets }) => (latestOffsets.length === 0 ? 0 : median(latestOffsets)) | ||
) | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
export interface State { | ||
latestOffsets: number[], // a buffer of the latest offset values to determine current offset | ||
} | ||
|
||
export const initialState: State = { | ||
latestOffsets: [], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { createFeatureAlt } from 'src/app/utils/store/feature_creator'; | ||
import { reducer } from './time-offset.reducer'; | ||
import { selectors } from './time-offset.selectors'; | ||
import * as actions from './time-offset.actions'; | ||
|
||
export const fromTimeOffset = createFeatureAlt({ | ||
name: 'timeOffset', | ||
reducer, | ||
extraSelectors: ({ selectTimeOffsetState }) => ({ ...selectors(selectTimeOffsetState) }), | ||
actionGroups: actions | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { median } from './array'; | ||
|
||
describe('median', () => { | ||
it('should throw error if the list is empty', () => { | ||
expect(() => median([])).toThrowError(); | ||
}); | ||
it('should work with a single element list', () => { | ||
expect(median([5])).toEqual(5); | ||
}); | ||
it('should work with a odd number of values', () => { | ||
expect(median([2, 4, 8])).toEqual(4); | ||
expect(median([4, 2, 8])).toEqual(4); | ||
expect(median([8, 2, 4])).toEqual(4); | ||
}); | ||
it('should work (avg the 2 medians) with an even number of values', () => { | ||
expect(median([2, 4, 5, 10])).toEqual(4.5); | ||
expect(median([5, 4, 2, 10])).toEqual(4.5); | ||
expect(median([4, 2, 10, 5])).toEqual(4.5); | ||
}); | ||
it('should not modify the input list', () => { | ||
const list = [4, 2, 3]; | ||
median(list); | ||
expect(list).toEqual([4, 2, 3]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters