[Fix] websocket connecting and patchDB connection monitoring (#1738)

* refactor how we handle rpc responses and patchdb connection monitoring

* websockets only

* remove unused global error handlers

* chore: clear storage inside auth service

* feat: convert all global toasts to declarative approach (#1754)

* no more reference to serverID

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2022-08-22 10:53:52 -06:00
committed by GitHub
parent 70baed88f4
commit 3ddeb5fa94
101 changed files with 1177 additions and 1298 deletions

View File

@@ -1,165 +1,49 @@
import { Inject, Injectable } from '@angular/core'
import { Storage } from '@ionic/storage-angular'
import { Bootstrapper, PatchDB, Source, Store } from 'patch-db-client'
import {
BehaviorSubject,
Observable,
of,
ReplaySubject,
Subscription,
} from 'rxjs'
import {
catchError,
debounceTime,
filter,
finalize,
mergeMap,
shareReplay,
switchMap,
take,
tap,
withLatestFrom,
} from 'rxjs/operators'
import { pauseFor } from '@start9labs/shared'
import { Bootstrapper, PatchDB, Store } from 'patch-db-client'
import { Observable, of, Subscription } from 'rxjs'
import { catchError, debounceTime, finalize, tap } from 'rxjs/operators'
import { DataModel } from './data-model'
import { ApiService } from '../api/embassy-api.service'
import { AuthService } from '../auth.service'
import { BOOTSTRAPPER, PATCH_SOURCE, PATCH_SOURCE$ } from './patch-db.factory'
export enum PatchConnection {
Initializing = 'initializing',
Connected = 'connected',
Disconnected = 'disconnected',
}
import { BOOTSTRAPPER } from './patch-db.factory'
@Injectable({
providedIn: 'root',
})
export class PatchDbService {
private readonly WS_SUCCESS = 'wsSuccess'
private readonly patchConnection$ = new ReplaySubject<PatchConnection>(1)
private readonly wsSuccess$ = new BehaviorSubject(false)
private readonly polling$ = new BehaviorSubject(false)
private subs: Subscription[] = []
readonly connected$ = this.watchPatchConnection$().pipe(
filter(status => status === PatchConnection.Connected),
take(1),
shareReplay(),
)
private sub?: Subscription
constructor(
// [wsSources, pollSources]
@Inject(PATCH_SOURCE) private readonly sources: Source<DataModel>[],
@Inject(BOOTSTRAPPER)
private readonly bootstrapper: Bootstrapper<DataModel>,
@Inject(PATCH_SOURCE$)
private readonly sources$: BehaviorSubject<Source<DataModel>[]>,
private readonly http: ApiService,
private readonly auth: AuthService,
private readonly storage: Storage,
private readonly patchDb: PatchDB<DataModel>,
) {}
init() {
this.sources$.next([this.sources[0], this.http])
this.patchConnection$.next(PatchConnection.Initializing)
}
start(): void {
// Early return if already started
if (this.sub) {
return
}
async start(): Promise<void> {
this.init()
this.subs.push(
// Connection Error
this.patchDb.connectionError$
.pipe(
debounceTime(420),
withLatestFrom(this.polling$),
mergeMap(async ([e, polling]) => {
if (polling) {
console.log('patchDB: POLLING FAILED', e)
this.patchConnection$.next(PatchConnection.Disconnected)
await pauseFor(2000)
this.sources$.next([this.sources[1], this.http])
return
}
console.log('patchDB: WEBSOCKET FAILED', e)
this.polling$.next(true)
this.sources$.next([this.sources[1], this.http])
}),
)
.subscribe({
complete: () => {
console.warn('patchDB: SYNC COMPLETE')
},
console.log('patchDB: STARTING')
this.sub = this.patchDb.cache$
.pipe(
debounceTime(420),
tap(cache => {
this.bootstrapper.update(cache)
}),
// RPC ERROR
this.patchDb.rpcError$
.pipe(
tap(({ error }) => {
if (error.code === 34) {
console.log('patchDB: Unauthorized. Logging out.')
this.auth.setUnverified()
}
}),
)
.subscribe({
complete: () => {
console.warn('patchDB: SYNC COMPLETE')
},
}),
// GOOD CONNECTION
this.patchDb.cache$
.pipe(
debounceTime(420),
withLatestFrom(this.patchConnection$, this.wsSuccess$, this.polling$),
tap(async ([cache, connection, wsSuccess, polling]) => {
this.bootstrapper.update(cache)
if (connection === PatchConnection.Initializing) {
console.log(
polling
? 'patchDB: POLL CONNECTED'
: 'patchDB: WEBSOCKET CONNECTED',
)
this.patchConnection$.next(PatchConnection.Connected)
if (!wsSuccess && !polling) {
console.log('patchDB: WEBSOCKET SUCCESS')
this.storage.set(this.WS_SUCCESS, 'true')
this.wsSuccess$.next(true)
}
} else if (
connection === PatchConnection.Disconnected &&
wsSuccess
) {
console.log('patchDB: SWITCHING BACK TO WEBSOCKETS')
this.patchConnection$.next(PatchConnection.Initializing)
this.polling$.next(false)
this.sources$.next([this.sources[0], this.http])
}
}),
)
.subscribe({
complete: () => {
console.warn('patchDB: SYNC COMPLETE')
},
}),
)
)
.subscribe()
}
stop(): void {
console.log('patchDB: STOPPING')
this.patchConnection$.next(PatchConnection.Initializing)
this.patchDb.store.reset()
this.subs.forEach(x => x.unsubscribe())
this.subs = []
}
// Early return if already stopped
if (!this.sub) {
return
}
watchPatchConnection$(): Observable<PatchConnection> {
return this.patchConnection$.asObservable()
console.log('patchDB: STOPPING')
this.patchDb.store.reset()
this.sub.unsubscribe()
this.sub = undefined
}
// prettier-ignore
@@ -168,10 +52,7 @@ export class PatchDbService {
console.log('patchDB: WATCHING ', argsString)
return this.patchConnection$.pipe(
filter(status => status === PatchConnection.Connected),
take(1),
switchMap(() => this.patchDb.store.watch$(...(args as []))),
return this.patchDb.store.watch$(...(args as [])).pipe(
tap(data => console.log('patchDB: NEW VALUE', argsString, data)),
catchError(e => {
console.error('patchDB: WATCH ERROR', e)