Skip to content

Commit 8f2cbac

Browse files
committed
fix(sync): ensure local changes are synced quickly before leaving the app
by immediate sync after editing data locally closes #2397
1 parent 8d8834b commit 8f2cbac

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

src/app/core/database/sync.service.spec.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { LoginState } from "../session/session-states/login-state.enum";
88
import { KeycloakAuthService } from "../session/auth/keycloak/keycloak-auth.service";
99
import { HttpStatusCode } from "@angular/common/http";
1010
import PouchDB from "pouchdb-browser";
11+
import { Subject } from "rxjs";
1112

1213
describe("SyncService", () => {
1314
let service: SyncService;
@@ -28,6 +29,15 @@ describe("SyncService", () => {
2829
loginState = TestBed.inject(LoginStateSubject);
2930
});
3031

32+
/**
33+
* ensure the interval for sync is stopped at end of test to avoid errors.
34+
* Somehow this does not work in afterEach().
35+
*/
36+
function stopPeriodicTimer() {
37+
service.liveSyncEnabled = false;
38+
tick(SyncService.SYNC_INTERVAL + 500);
39+
}
40+
3141
it("should be created", () => {
3242
expect(service).toBeTruthy();
3343
});
@@ -59,8 +69,7 @@ describe("SyncService", () => {
5969
tick(SyncService.SYNC_INTERVAL);
6070
expect(mockLocalDb.sync).toHaveBeenCalled();
6171

62-
service.liveSyncEnabled = false;
63-
tick(SyncService.SYNC_INTERVAL);
72+
stopPeriodicTimer();
6473
}));
6574

6675
it("should try auto-login if fetch fails and fetch again", fakeAsync(() => {
@@ -101,7 +110,31 @@ describe("SyncService", () => {
101110
expect(mockAuthService.login).toHaveBeenCalled();
102111
expect(mockAuthService.addAuthHeader).toHaveBeenCalledTimes(2);
103112

104-
service.liveSyncEnabled = false;
105-
tick(SyncService.SYNC_INTERVAL);
113+
stopPeriodicTimer();
114+
}));
115+
116+
it("should sync immediately when local db has changes", fakeAsync(() => {
117+
const mockLocalDb = jasmine.createSpyObj(["sync"]);
118+
const db = TestBed.inject(Database) as PouchDatabase;
119+
spyOn(db, "getPouchDB").and.returnValue(mockLocalDb);
120+
const mockChanges = new Subject();
121+
spyOn(db, "changes").and.returnValue(mockChanges);
122+
123+
loginState.next(LoginState.LOGGED_IN);
124+
125+
service.startSync();
126+
127+
mockLocalDb.sync.and.resolveTo({});
128+
tick(1000);
129+
expect(mockLocalDb.sync).toHaveBeenCalled();
130+
mockLocalDb.sync.calls.reset();
131+
expect(mockLocalDb.sync).not.toHaveBeenCalled();
132+
133+
// simulate local doc written
134+
mockChanges.next({});
135+
tick(500); // sync has a short debounce time
136+
expect(mockLocalDb.sync).toHaveBeenCalled();
137+
138+
stopPeriodicTimer();
106139
}));
107140
});

src/app/core/database/sync.service.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ import { HttpStatusCode } from "@angular/common/http";
66
import PouchDB from "pouchdb-browser";
77
import { SyncState } from "../session/session-states/sync-state.enum";
88
import { SyncStateSubject } from "../session/session-type";
9-
import { filter, mergeMap, repeat, retry, takeWhile } from "rxjs/operators";
9+
import {
10+
debounceTime,
11+
filter,
12+
mergeMap,
13+
retry,
14+
takeWhile,
15+
} from "rxjs/operators";
1016
import { KeycloakAuthService } from "../session/auth/keycloak/keycloak-auth.service";
1117
import { Config } from "../config/config";
1218
import { Entity } from "../entity/model/entity";
13-
import { from, of } from "rxjs";
19+
import { from, interval, merge, of } from "rxjs";
1420
import { environment } from "../../../environments/environment";
1521

1622
/**
@@ -140,11 +146,18 @@ export class SyncService {
140146
private liveSync() {
141147
this.liveSyncEnabled = true;
142148

143-
of(true)
149+
merge(
150+
// do an initial sync immediately
151+
of(true),
152+
// re-sync at regular interval
153+
interval(SyncService.SYNC_INTERVAL),
154+
// and immediately sync to upload any local changes
155+
this.database.changes(""),
156+
)
144157
.pipe(
158+
debounceTime(500),
145159
mergeMap(() => from(this.sync())),
146160
retry({ delay: SyncService.SYNC_INTERVAL }),
147-
repeat({ delay: SyncService.SYNC_INTERVAL }),
148161
takeWhile(() => this.liveSyncEnabled),
149162
)
150163
.subscribe();

0 commit comments

Comments
 (0)