diff options
Diffstat (limited to 'src/state/models')
-rw-r--r-- | src/state/models/me.ts | 42 | ||||
-rw-r--r-- | src/state/models/root-store.ts | 15 | ||||
-rw-r--r-- | src/state/models/session.ts | 40 |
3 files changed, 88 insertions, 9 deletions
diff --git a/src/state/models/me.ts b/src/state/models/me.ts index e3405b80d..fde387ebe 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {RootStoreModel} from './root-store' import {MembershipsViewModel} from './memberships-view' import {NotificationsViewModel} from './notifications-view' +import {isObj, hasProp} from '../lib/type-guards' export class MeModel { did?: string @@ -13,7 +14,11 @@ export class MeModel { notifications: NotificationsViewModel constructor(public rootStore: RootStoreModel) { - makeAutoObservable(this, {rootStore: false}, {autoBind: true}) + makeAutoObservable( + this, + {rootStore: false, serialize: false, hydrate: false}, + {autoBind: true}, + ) this.notifications = new NotificationsViewModel(this.rootStore, {}) } @@ -26,9 +31,42 @@ export class MeModel { this.memberships = undefined } + serialize(): unknown { + return { + did: this.did, + handle: this.handle, + displayName: this.displayName, + description: this.description, + } + } + + hydrate(v: unknown) { + if (isObj(v)) { + let did, handle, displayName, description + if (hasProp(v, 'did') && typeof v.did === 'string') { + did = v.did + } + if (hasProp(v, 'handle') && typeof v.handle === 'string') { + handle = v.handle + } + if (hasProp(v, 'displayName') && typeof v.displayName === 'string') { + displayName = v.displayName + } + if (hasProp(v, 'description') && typeof v.description === 'string') { + description = v.description + } + if (did && handle) { + this.did = did + this.handle = handle + this.displayName = displayName + this.description = description + } + } + } + async load() { const sess = this.rootStore.session - if (sess.isAuthed && sess.data) { + if (sess.hasSession && sess.data) { this.did = sess.data.did || '' this.handle = sess.data.handle const profile = await this.rootStore.api.app.bsky.actor.getProfile({ diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index af79ccc1e..ad306ee9f 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -14,6 +14,7 @@ import {ProfilesViewModel} from './profiles-view' import {LinkMetasViewModel} from './link-metas-view' import {MeModel} from './me' import {OnboardModel} from './onboard' +import {isNetworkError} from '../../lib/errors' export class RootStoreModel { session = new SessionModel(this) @@ -45,12 +46,18 @@ export class RootStoreModel { } async fetchStateUpdate() { - if (!this.session.isAuthed) { + if (!this.session.hasSession) { return } try { + if (!this.session.online) { + await this.session.connect() + } await this.me.fetchStateUpdate() - } catch (e) { + } catch (e: unknown) { + if (isNetworkError(e)) { + this.session.setOnline(false) // connection lost + } console.error('Failed to fetch latest state', e) } } @@ -58,6 +65,7 @@ export class RootStoreModel { serialize(): unknown { return { session: this.session.serialize(), + me: this.me.serialize(), nav: this.nav.serialize(), onboard: this.onboard.serialize(), } @@ -68,6 +76,9 @@ export class RootStoreModel { if (hasProp(v, 'session')) { this.session.hydrate(v.session) } + if (hasProp(v, 'me')) { + this.me.hydrate(v.me) + } if (hasProp(v, 'nav')) { this.nav.hydrate(v.nav) } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index 0f1faeaba..069e3db32 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -7,6 +7,7 @@ import type { import type * as GetAccountsConfig from '../../third-party/api/src/client/types/com/atproto/server/getAccountsConfig' import {isObj, hasProp} from '../lib/type-guards' import {RootStoreModel} from './root-store' +import {isNetworkError} from '../../lib/errors' export type ServiceDescription = GetAccountsConfig.OutputSchema @@ -20,16 +21,20 @@ interface SessionData { export class SessionModel { data: SessionData | null = null + online = false + attemptingConnect = false + private _connectPromise: Promise<void> | undefined constructor(public rootStore: RootStoreModel) { makeAutoObservable(this, { rootStore: false, serialize: false, hydrate: false, + _connectPromise: false, }) } - get isAuthed() { + get hasSession() { return this.data !== null } @@ -91,6 +96,13 @@ export class SessionModel { this.data = data } + setOnline(online: boolean, attemptingConnect?: boolean) { + this.online = online + if (typeof attemptingConnect === 'boolean') { + this.attemptingConnect = attemptingConnect + } + } + updateAuthTokens(session: Session) { if (this.data) { this.setState({ @@ -125,7 +137,14 @@ export class SessionModel { return true } - async setup(): Promise<void> { + async connect(): Promise<void> { + this._connectPromise ??= this._connect() + await this._connectPromise + this._connectPromise = undefined + } + + private async _connect(): Promise<void> { + this.attemptingConnect = true if (!this.configureApi()) { return } @@ -133,14 +152,25 @@ export class SessionModel { try { const sess = await this.rootStore.api.com.atproto.session.get() if (sess.success && this.data && this.data.did === sess.data.did) { + this.setOnline(true, false) + if (this.rootStore.me.did !== sess.data.did) { + this.rootStore.me.clear() + } this.rootStore.me.load().catch(e => { console.error('Failed to fetch local user information', e) }) return // success } - } catch (e: any) {} + } catch (e: any) { + if (isNetworkError(e)) { + this.setOnline(false, false) // connection issue + return + } else { + this.clear() // invalid session cached + } + } - this.clear() // invalid session cached + this.setOnline(false, false) } async describeService(service: string): Promise<ServiceDescription> { @@ -212,7 +242,7 @@ export class SessionModel { } async logout() { - if (this.isAuthed) { + if (this.hasSession) { this.rootStore.api.com.atproto.session.delete().catch((e: any) => { console.error('(Minor issue) Failed to delete session on the server', e) }) |