about summary refs log tree commit diff
path: root/src/state/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models')
-rw-r--r--src/state/models/feed-view.ts1
-rw-r--r--src/state/models/log.ts94
-rw-r--r--src/state/models/me.ts15
-rw-r--r--src/state/models/notifications-view.ts17
-rw-r--r--src/state/models/profile-ui.ts16
-rw-r--r--src/state/models/profile-view.ts1
-rw-r--r--src/state/models/root-store.ts11
-rw-r--r--src/state/models/session.ts26
-rw-r--r--src/state/models/suggested-invites-view.ts14
9 files changed, 165 insertions, 30 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts
index b5a3b29d7..c8827b1fb 100644
--- a/src/state/models/feed-view.ts
+++ b/src/state/models/feed-view.ts
@@ -405,7 +405,6 @@ export class FeedModel {
         cursor = this.feed[res.data.feed.length - 1]
           ? ts(this.feed[res.data.feed.length - 1])
           : undefined
-        console.log(numToFetch, cursor, res.data.feed.length)
       } while (numToFetch > 0)
       this._xIdle()
     } catch (e: any) {
diff --git a/src/state/models/log.ts b/src/state/models/log.ts
new file mode 100644
index 000000000..42172a3b1
--- /dev/null
+++ b/src/state/models/log.ts
@@ -0,0 +1,94 @@
+import {makeAutoObservable} from 'mobx'
+import {isObj, hasProp} from '../lib/type-guards'
+
+interface LogEntry {
+  id: string
+  type?: string
+  summary?: string
+  details?: string
+  ts?: number
+}
+
+let _lastTs: string
+let _lastId: string
+function genId(): string {
+  let candidate = String(Date.now())
+  if (_lastTs === candidate) {
+    const id = _lastId + 'x'
+    _lastId = id
+    return id
+  }
+  _lastTs = candidate
+  _lastId = candidate
+  return candidate
+}
+
+export class LogModel {
+  entries: LogEntry[] = []
+
+  constructor() {
+    makeAutoObservable(this, {serialize: false, hydrate: false})
+  }
+
+  serialize(): unknown {
+    return {
+      entries: this.entries.slice(-100),
+    }
+  }
+
+  hydrate(v: unknown) {
+    if (isObj(v)) {
+      if (hasProp(v, 'entries') && Array.isArray(v.entries)) {
+        this.entries = v.entries.filter(
+          e => isObj(e) && hasProp(e, 'id') && typeof e.id === 'string',
+        )
+      }
+    }
+  }
+
+  private add(entry: LogEntry) {
+    this.entries.push(entry)
+  }
+
+  debug(summary: string, details?: any) {
+    if (details && typeof details !== 'string') {
+      details = JSON.stringify(details, null, 2)
+    }
+    console.debug(summary, details || '')
+    this.add({
+      id: genId(),
+      type: 'debug',
+      summary,
+      details,
+      ts: Date.now(),
+    })
+  }
+
+  warn(summary: string, details?: any) {
+    if (details && typeof details !== 'string') {
+      details = JSON.stringify(details, null, 2)
+    }
+    console.warn(summary, details || '')
+    this.add({
+      id: genId(),
+      type: 'warn',
+      summary,
+      details,
+      ts: Date.now(),
+    })
+  }
+
+  error(summary: string, details?: any) {
+    if (details && typeof details !== 'string') {
+      details = JSON.stringify(details, null, 2)
+    }
+    console.error(summary, details || '')
+    this.add({
+      id: genId(),
+      type: 'error',
+      summary,
+      details,
+      ts: Date.now(),
+    })
+  }
+}
diff --git a/src/state/models/me.ts b/src/state/models/me.ts
index 9591acb80..ae1e6aed2 100644
--- a/src/state/models/me.ts
+++ b/src/state/models/me.ts
@@ -104,13 +104,22 @@ export class MeModel {
       })
       await Promise.all([
         this.memberships?.setup().catch(e => {
-          console.error('Failed to setup memberships model', e)
+          this.rootStore.log.error(
+            'Failed to setup memberships model',
+            e.toString(),
+          )
         }),
         this.mainFeed.setup().catch(e => {
-          console.error('Failed to setup main feed model', e)
+          this.rootStore.log.error(
+            'Failed to setup main feed model',
+            e.toString(),
+          )
         }),
         this.notifications.setup().catch(e => {
-          console.error('Failed to setup notifications model', e)
+          this.rootStore.log.error(
+            'Failed to setup notifications model',
+            e.toString(),
+          )
         }),
       ])
     } else {
diff --git a/src/state/models/notifications-view.ts b/src/state/models/notifications-view.ts
index c1ee78d41..38a8ca133 100644
--- a/src/state/models/notifications-view.ts
+++ b/src/state/models/notifications-view.ts
@@ -149,7 +149,10 @@ export class NotificationsViewItemModel implements GroupedNotification {
         depth: 0,
       })
       await this.additionalPost.setup().catch(e => {
-        console.error('Failed to load post needed by notification', e)
+        this.rootStore.log.error(
+          'Failed to load post needed by notification',
+          e.toString(),
+        )
       })
     }
   }
@@ -262,8 +265,11 @@ export class NotificationsViewModel {
         seenAt: new Date().toISOString(),
       })
       this.rootStore.me.clearNotificationCount()
-    } catch (e) {
-      console.log('Failed to update notifications read state', e)
+    } catch (e: any) {
+      this.rootStore.log.warn(
+        'Failed to update notifications read state',
+        e.toString(),
+      )
     }
   }
 
@@ -350,7 +356,6 @@ export class NotificationsViewModel {
         this._updateAll(res)
         numToFetch -= res.data.notifications.length
         cursor = this.notifications[res.data.notifications.length - 1].indexedAt
-        console.log(numToFetch, cursor, res.data.notifications.length)
       } while (numToFetch > 0)
       this._xIdle()
     } catch (e: any) {
@@ -379,9 +384,9 @@ export class NotificationsViewModel {
       itemModels.push(itemModel)
     }
     await Promise.all(promises).catch(e => {
-      console.error(
+      this.rootStore.log.error(
         'Uncaught failure during notifications-view _appendAll()',
-        e,
+        e.toString(),
       )
     })
     runInAction(() => {
diff --git a/src/state/models/profile-ui.ts b/src/state/models/profile-ui.ts
index d0eb1f858..081160e65 100644
--- a/src/state/models/profile-ui.ts
+++ b/src/state/models/profile-ui.ts
@@ -114,20 +114,28 @@ export class ProfileUiModel {
     await Promise.all([
       this.profile
         .setup()
-        .catch(err => console.error('Failed to fetch profile', err)),
+        .catch(err =>
+          this.rootStore.log.error('Failed to fetch profile', err.toString()),
+        ),
       this.feed
         .setup()
-        .catch(err => console.error('Failed to fetch feed', err)),
+        .catch(err =>
+          this.rootStore.log.error('Failed to fetch feed', err.toString()),
+        ),
     ])
     if (this.isUser) {
       await this.memberships
         .setup()
-        .catch(err => console.error('Failed to fetch members', err))
+        .catch(err =>
+          this.rootStore.log.error('Failed to fetch members', err.toString()),
+        )
     }
     if (this.isScene) {
       await this.members
         .setup()
-        .catch(err => console.error('Failed to fetch members', err))
+        .catch(err =>
+          this.rootStore.log.error('Failed to fetch members', err.toString()),
+        )
     }
   }
 
diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts
index 2a69a1345..1c825a482 100644
--- a/src/state/models/profile-view.ts
+++ b/src/state/models/profile-view.ts
@@ -203,7 +203,6 @@ export class ProfileViewModel {
   }
 
   private _replaceAll(res: GetProfile.Response) {
-    console.log(res.data)
     this.did = res.data.did
     this.handle = res.data.handle
     Object.assign(this.declaration, res.data.declaration)
diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts
index 54578b4a5..0166b67e6 100644
--- a/src/state/models/root-store.ts
+++ b/src/state/models/root-store.ts
@@ -6,6 +6,7 @@ import {makeAutoObservable} from 'mobx'
 import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
 import {createContext, useContext} from 'react'
 import {isObj, hasProp} from '../lib/type-guards'
+import {LogModel} from './log'
 import {SessionModel} from './session'
 import {NavigationModel} from './navigation'
 import {ShellUiModel} from './shell-ui'
@@ -16,6 +17,7 @@ import {OnboardModel} from './onboard'
 import {isNetworkError} from '../../lib/errors'
 
 export class RootStoreModel {
+  log = new LogModel()
   session = new SessionModel(this)
   nav = new NavigationModel()
   shell = new ShellUiModel()
@@ -53,16 +55,17 @@ export class RootStoreModel {
         await this.session.connect()
       }
       await this.me.fetchStateUpdate()
-    } catch (e: unknown) {
+    } catch (e: any) {
       if (isNetworkError(e)) {
         this.session.setOnline(false) // connection lost
       }
-      console.error('Failed to fetch latest state', e)
+      this.log.error('Failed to fetch latest state', e.toString())
     }
   }
 
   serialize(): unknown {
     return {
+      log: this.log.serialize(),
       session: this.session.serialize(),
       me: this.me.serialize(),
       nav: this.nav.serialize(),
@@ -73,8 +76,8 @@ export class RootStoreModel {
 
   hydrate(v: unknown) {
     if (isObj(v)) {
-      if (hasProp(v, 'session')) {
-        this.session.hydrate(v.session)
+      if (hasProp(v, 'log')) {
+        this.log.hydrate(v.log)
       }
       if (hasProp(v, 'me')) {
         this.me.hydrate(v.me)
diff --git a/src/state/models/session.ts b/src/state/models/session.ts
index febffcec3..3efb5d2a6 100644
--- a/src/state/models/session.ts
+++ b/src/state/models/session.ts
@@ -121,11 +121,11 @@ export class SessionModel {
     try {
       const serviceUri = new URL(this.data.service)
       this.rootStore.api.xrpc.uri = serviceUri
-    } catch (e) {
-      console.error(
+    } catch (e: any) {
+      this.rootStore.log.error(
         `Invalid service URL: ${this.data.service}. Resetting session.`,
+        e.toString(),
       )
-      console.error(e)
       this.clear()
       return false
     }
@@ -160,7 +160,10 @@ export class SessionModel {
           this.rootStore.me.clear()
         }
         this.rootStore.me.load().catch(e => {
-          console.error('Failed to fetch local user information', e)
+          this.rootStore.log.error(
+            'Failed to fetch local user information',
+            e.toString(),
+          )
         })
         return // success
       }
@@ -204,7 +207,10 @@ export class SessionModel {
       this.configureApi()
       this.setOnline(true, false)
       this.rootStore.me.load().catch(e => {
-        console.error('Failed to fetch local user information', e)
+        this.rootStore.log.error(
+          'Failed to fetch local user information',
+          e.toString(),
+        )
       })
     }
   }
@@ -240,7 +246,10 @@ export class SessionModel {
       this.rootStore.onboard.start()
       this.configureApi()
       this.rootStore.me.load().catch(e => {
-        console.error('Failed to fetch local user information', e)
+        this.rootStore.log.error(
+          'Failed to fetch local user information',
+          e.toString(),
+        )
       })
     }
   }
@@ -248,7 +257,10 @@ export class SessionModel {
   async logout() {
     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)
+        this.rootStore.log.warn(
+          '(Minor issue) Failed to delete session on the server',
+          e,
+        )
       })
     }
     this.rootStore.clearAll()
diff --git a/src/state/models/suggested-invites-view.ts b/src/state/models/suggested-invites-view.ts
index 33ca7396e..eb0665bca 100644
--- a/src/state/models/suggested-invites-view.ts
+++ b/src/state/models/suggested-invites-view.ts
@@ -98,8 +98,11 @@ export class SuggestedInvitesView {
     try {
       // TODO need to fetch all!
       await this.sceneAssertionsView.setup()
-    } catch (e) {
-      console.error(e)
+    } catch (e: any) {
+      this.rootStore.log.error(
+        'Failed to fetch current scene members in suggested invites',
+        e.toString(),
+      )
       this._xIdle(
         'Failed to fetch the current scene members. Check your internet connection and try again.',
       )
@@ -107,8 +110,11 @@ export class SuggestedInvitesView {
     }
     try {
       await this.myFollowsView.setup()
-    } catch (e) {
-      console.error(e)
+    } catch (e: any) {
+      this.rootStore.log.error(
+        'Failed to fetch current followers in suggested invites',
+        e.toString(),
+      )
       this._xIdle(
         'Failed to fetch the your current followers. Check your internet connection and try again.',
       )