diff options
Diffstat (limited to 'src/state')
25 files changed, 329 insertions, 446 deletions
diff --git a/src/state/index.ts b/src/state/index.ts index f0713efeb..4755c28f4 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,6 +1,6 @@ import {autorun} from 'mobx' import {AppState, Platform} from 'react-native' -import {AtpAgent} from '@atproto/api' +import {BskyAgent} from '@atproto/api' import {RootStoreModel} from './models/root-store' import * as apiPolyfill from 'lib/api/api-polyfill' import * as storage from 'lib/storage' @@ -19,7 +19,7 @@ export async function setupState(serviceUri = DEFAULT_SERVICE) { apiPolyfill.doPolyfill() - rootStore = new RootStoreModel(new AtpAgent({service: serviceUri})) + rootStore = new RootStoreModel(new BskyAgent({service: serviceUri})) try { data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {} rootStore.log.debug('Initial hydrate', {hasSession: !!data.session}) diff --git a/src/state/models/cache/image-sizes.ts b/src/state/models/cache/image-sizes.ts index ff0486278..2fd6e0013 100644 --- a/src/state/models/cache/image-sizes.ts +++ b/src/state/models/cache/image-sizes.ts @@ -3,7 +3,7 @@ import {Dim} from 'lib/media/manip' export class ImageSizesCache { sizes: Map<string, Dim> = new Map() - private activeRequests: Map<string, Promise<Dim>> = new Map() + activeRequests: Map<string, Promise<Dim>> = new Map() constructor() {} diff --git a/src/state/models/cache/my-follows.ts b/src/state/models/cache/my-follows.ts index 725b7841e..14eeaae21 100644 --- a/src/state/models/cache/my-follows.ts +++ b/src/state/models/cache/my-follows.ts @@ -1,15 +1,12 @@ import {makeAutoObservable, runInAction} from 'mobx' -import {FollowRecord, AppBskyActorProfile, AppBskyActorRef} from '@atproto/api' +import {FollowRecord, AppBskyActorDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' import {bundleAsync} from 'lib/async/bundle' const CACHE_TTL = 1000 * 60 * 60 // hourly type FollowsListResponse = Awaited<ReturnType<FollowRecord['list']>> type FollowsListResponseRecord = FollowsListResponse['records'][0] -type Profile = - | AppBskyActorProfile.ViewBasic - | AppBskyActorProfile.View - | AppBskyActorRef.WithInfo +type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView /** * This model is used to maintain a synced local cache of the user's @@ -53,21 +50,21 @@ export class MyFollowsCache { fetch = bundleAsync(async () => { this.rootStore.log.debug('MyFollowsModel:fetch running full fetch') - let before + let rkeyStart let records: FollowsListResponseRecord[] = [] do { const res: FollowsListResponse = - await this.rootStore.api.app.bsky.graph.follow.list({ - user: this.rootStore.me.did, - before, + await this.rootStore.agent.app.bsky.graph.follow.list({ + repo: this.rootStore.me.did, + rkeyStart, }) records = records.concat(res.records) - before = res.cursor - } while (typeof before !== 'undefined') + rkeyStart = res.cursor + } while (typeof rkeyStart !== 'undefined') runInAction(() => { this.followDidToRecordMap = {} for (const record of records) { - this.followDidToRecordMap[record.value.subject.did] = record.uri + this.followDidToRecordMap[record.value.subject] = record.uri } this.lastSync = Date.now() this.myDid = this.rootStore.me.did diff --git a/src/state/models/discovery/foafs.ts b/src/state/models/discovery/foafs.ts index 241338a16..27cee8503 100644 --- a/src/state/models/discovery/foafs.ts +++ b/src/state/models/discovery/foafs.ts @@ -1,15 +1,15 @@ -import {AppBskyActorProfile, AppBskyActorRef} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import {makeAutoObservable, runInAction} from 'mobx' import sampleSize from 'lodash.samplesize' import {bundleAsync} from 'lib/async/bundle' import {RootStoreModel} from '../root-store' -export type RefWithInfoAndFollowers = AppBskyActorRef.WithInfo & { - followers: AppBskyActorProfile.View[] +export type RefWithInfoAndFollowers = AppBskyActorDefs.ProfileViewBasic & { + followers: AppBskyActorDefs.ProfileView[] } -export type ProfileViewFollows = AppBskyActorProfile.View & { - follows: AppBskyActorRef.WithInfo[] +export type ProfileViewFollows = AppBskyActorDefs.ProfileView & { + follows: AppBskyActorDefs.ProfileViewBasic[] } export class FoafsModel { @@ -51,14 +51,14 @@ export class FoafsModel { this.popular.length = 0 // fetch their profiles - const profiles = await this.rootStore.api.app.bsky.actor.getProfiles({ + const profiles = await this.rootStore.agent.getProfiles({ actors: this.sources, }) // fetch their follows const results = await Promise.allSettled( this.sources.map(source => - this.rootStore.api.app.bsky.graph.getFollows({user: source}), + this.rootStore.agent.getFollows({actor: source}), ), ) diff --git a/src/state/models/discovery/suggested-actors.ts b/src/state/models/discovery/suggested-actors.ts index cf8e2dd7b..91c5efd02 100644 --- a/src/state/models/discovery/suggested-actors.ts +++ b/src/state/models/discovery/suggested-actors.ts @@ -1,5 +1,5 @@ import {makeAutoObservable, runInAction} from 'mobx' -import {AppBskyActorProfile as Profile} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import shuffle from 'lodash.shuffle' import {RootStoreModel} from '../root-store' import {cleanError} from 'lib/strings/errors' @@ -8,7 +8,9 @@ import {SUGGESTED_FOLLOWS} from 'lib/constants' const PAGE_SIZE = 30 -export type SuggestedActor = Profile.ViewBasic | Profile.View +export type SuggestedActor = + | AppBskyActorDefs.ProfileViewBasic + | AppBskyActorDefs.ProfileView export class SuggestedActorsModel { // state @@ -20,7 +22,7 @@ export class SuggestedActorsModel { hasMore = true loadMoreCursor?: string - private hardCodedSuggestions: SuggestedActor[] | undefined + hardCodedSuggestions: SuggestedActor[] | undefined // data suggestions: SuggestedActor[] = [] @@ -82,7 +84,7 @@ export class SuggestedActorsModel { this.loadMoreCursor = undefined } else { // pull from the PDS' algo - res = await this.rootStore.api.app.bsky.actor.getSuggestions({ + res = await this.rootStore.agent.app.bsky.actor.getSuggestions({ limit: this.pageSize, cursor: this.loadMoreCursor, }) @@ -104,7 +106,7 @@ export class SuggestedActorsModel { } }) - private async fetchHardcodedSuggestions() { + async fetchHardcodedSuggestions() { if (this.hardCodedSuggestions) { return } @@ -118,9 +120,9 @@ export class SuggestedActorsModel { ] // fetch the profiles in chunks of 25 (the limit allowed by `getProfiles`) - let profiles: Profile.View[] = [] + let profiles: AppBskyActorDefs.ProfileView[] = [] do { - const res = await this.rootStore.api.app.bsky.actor.getProfiles({ + const res = await this.rootStore.agent.getProfiles({ actors: actors.splice(0, 25), }) profiles = profiles.concat(res.data.profiles) @@ -152,13 +154,13 @@ export class SuggestedActorsModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index 083863fe2..8b62c958f 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -1,32 +1,29 @@ import {makeAutoObservable, runInAction} from 'mobx' import { AppBskyFeedGetTimeline as GetTimeline, - AppBskyFeedFeedViewPost, + AppBskyFeedDefs, AppBskyFeedPost, AppBskyFeedGetAuthorFeed as GetAuthorFeed, + RichText, } from '@atproto/api' import AwaitLock from 'await-lock' import {bundleAsync} from 'lib/async/bundle' import sampleSize from 'lodash.samplesize' -type FeedViewPost = AppBskyFeedFeedViewPost.Main -type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost -type PostView = AppBskyFeedPost.View -import {AtUri} from '../../third-party/uri' import {RootStoreModel} from './root-store' -import * as apilib from 'lib/api/index' import {cleanError} from 'lib/strings/errors' -import {RichText} from 'lib/strings/rich-text' import {SUGGESTED_FOLLOWS} from 'lib/constants' import { getCombinedCursors, getMultipleAuthorsPosts, mergePosts, } from 'lib/api/build-suggested-posts' - import {FeedTuner, FeedViewPostsSlice} from 'lib/api/feed-manip' -const PAGE_SIZE = 30 +type FeedViewPost = AppBskyFeedDefs.FeedViewPost +type ReasonRepost = AppBskyFeedDefs.ReasonRepost +type PostView = AppBskyFeedDefs.PostView +const PAGE_SIZE = 30 let _idCounter = 0 export class FeedItemModel { @@ -51,11 +48,7 @@ export class FeedItemModel { const valid = AppBskyFeedPost.validateRecord(this.post.record) if (valid.success) { this.postRecord = this.post.record - this.richText = new RichText( - this.postRecord.text, - this.postRecord.entities, - {cleanNewlines: true}, - ) + this.richText = new RichText(this.postRecord, {cleanNewlines: true}) } else { rootStore.log.warn( 'Received an invalid app.bsky.feed.post record', @@ -82,7 +75,7 @@ export class FeedItemModel { copyMetrics(v: FeedViewPost) { this.post.replyCount = v.post.replyCount this.post.repostCount = v.post.repostCount - this.post.upvoteCount = v.post.upvoteCount + this.post.likeCount = v.post.likeCount this.post.viewer = v.post.viewer } @@ -92,68 +85,43 @@ export class FeedItemModel { } } - async toggleUpvote() { - const wasUpvoted = !!this.post.viewer.upvote - const wasDownvoted = !!this.post.viewer.downvote - const res = await this.rootStore.api.app.bsky.feed.setVote({ - subject: { - uri: this.post.uri, - cid: this.post.cid, - }, - direction: wasUpvoted ? 'none' : 'up', - }) - runInAction(() => { - if (wasDownvoted) { - this.post.downvoteCount-- - } - if (wasUpvoted) { - this.post.upvoteCount-- - } else { - this.post.upvoteCount++ - } - this.post.viewer.upvote = res.data.upvote - this.post.viewer.downvote = res.data.downvote - }) - } - - async toggleDownvote() { - const wasUpvoted = !!this.post.viewer.upvote - const wasDownvoted = !!this.post.viewer.downvote - const res = await this.rootStore.api.app.bsky.feed.setVote({ - subject: { - uri: this.post.uri, - cid: this.post.cid, - }, - direction: wasDownvoted ? 'none' : 'down', - }) - runInAction(() => { - if (wasUpvoted) { - this.post.upvoteCount-- - } - if (wasDownvoted) { - this.post.downvoteCount-- - } else { - this.post.downvoteCount++ - } - this.post.viewer.upvote = res.data.upvote - this.post.viewer.downvote = res.data.downvote - }) + async toggleLike() { + if (this.post.viewer?.like) { + await this.rootStore.agent.deleteLike(this.post.viewer.like) + runInAction(() => { + this.post.likeCount = this.post.likeCount || 0 + this.post.viewer = this.post.viewer || {} + this.post.likeCount-- + this.post.viewer.like = undefined + }) + } else { + const res = await this.rootStore.agent.like(this.post.uri, this.post.cid) + runInAction(() => { + this.post.likeCount = this.post.likeCount || 0 + this.post.viewer = this.post.viewer || {} + this.post.likeCount++ + this.post.viewer.like = res.uri + }) + } } async toggleRepost() { - if (this.post.viewer.repost) { - await apilib.unrepost(this.rootStore, this.post.viewer.repost) + if (this.post.viewer?.repost) { + await this.rootStore.agent.deleteRepost(this.post.viewer.repost) runInAction(() => { + this.post.repostCount = this.post.repostCount || 0 + this.post.viewer = this.post.viewer || {} this.post.repostCount-- this.post.viewer.repost = undefined }) } else { - const res = await apilib.repost( - this.rootStore, + const res = await this.rootStore.agent.repost( this.post.uri, this.post.cid, ) runInAction(() => { + this.post.repostCount = this.post.repostCount || 0 + this.post.viewer = this.post.viewer || {} this.post.repostCount++ this.post.viewer.repost = res.uri }) @@ -161,10 +129,7 @@ export class FeedItemModel { } async delete() { - await this.rootStore.api.app.bsky.feed.post.delete({ - did: this.post.author.did, - rkey: new AtUri(this.post.uri).rkey, - }) + await this.rootStore.agent.deletePost(this.post.uri) this.rootStore.emitPostDeleted(this.post.uri) } } @@ -250,7 +215,7 @@ export class FeedModel { tuner = new FeedTuner() // used to linearize async modifications to state - private lock = new AwaitLock() + lock = new AwaitLock() // data slices: FeedSliceModel[] = [] @@ -291,8 +256,8 @@ export class FeedModel { const params = this.params as GetAuthorFeed.QueryParams const item = slice.rootItem const isRepost = - item?.reasonRepost?.by?.handle === params.author || - item?.reasonRepost?.by?.did === params.author + item?.reasonRepost?.by?.handle === params.actor || + item?.reasonRepost?.by?.did === params.actor return ( !item.reply || // not a reply isRepost || // but allow if it's a repost @@ -338,7 +303,7 @@ export class FeedModel { return this.setup() } - private get feedTuners() { + get feedTuners() { if (this.feedType === 'goodstuff') { return [ FeedTuner.dedupReposts, @@ -406,7 +371,7 @@ export class FeedModel { this._xLoading() try { const res = await this._getFeed({ - before: this.loadMoreCursor, + cursor: this.loadMoreCursor, limit: PAGE_SIZE, }) await this._appendAll(res) @@ -439,7 +404,7 @@ export class FeedModel { try { do { const res: GetTimeline.Response = await this._getFeed({ - before: cursor, + cursor, limit: Math.min(numToFetch, 100), }) if (res.data.feed.length === 0) { @@ -478,14 +443,18 @@ export class FeedModel { new FeedSliceModel(this.rootStore, `item-${_idCounter++}`, slice), ) if (autoPrepend) { - this.slices = nextSlicesModels.concat( - this.slices.filter(slice1 => - nextSlicesModels.find(slice2 => slice1.uri === slice2.uri), - ), - ) - this.setHasNewLatest(false) + runInAction(() => { + this.slices = nextSlicesModels.concat( + this.slices.filter(slice1 => + nextSlicesModels.find(slice2 => slice1.uri === slice2.uri), + ), + ) + this.setHasNewLatest(false) + }) } else { - this.nextSlices = nextSlicesModels + runInAction(() => { + this.nextSlices = nextSlicesModels + }) this.setHasNewLatest(true) } } else { @@ -519,13 +488,13 @@ export class FeedModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -538,14 +507,12 @@ export class FeedModel { // helper functions // = - private async _replaceAll( - res: GetTimeline.Response | GetAuthorFeed.Response, - ) { + async _replaceAll(res: GetTimeline.Response | GetAuthorFeed.Response) { this.pollCursor = res.data.feed[0]?.post.uri return this._appendAll(res, true) } - private async _appendAll( + async _appendAll( res: GetTimeline.Response | GetAuthorFeed.Response, replace = false, ) { @@ -572,7 +539,7 @@ export class FeedModel { }) } - private _updateAll(res: GetTimeline.Response | GetAuthorFeed.Response) { + _updateAll(res: GetTimeline.Response | GetAuthorFeed.Response) { for (const item of res.data.feed) { const existingSlice = this.slices.find(slice => slice.containsUri(item.post.uri), @@ -596,7 +563,7 @@ export class FeedModel { const responses = await getMultipleAuthorsPosts( this.rootStore, sampleSize(SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)), 20), - params.before, + params.cursor, 20, ) const combinedCursor = getCombinedCursors(responses) @@ -611,9 +578,7 @@ export class FeedModel { headers: lastHeaders, } } else if (this.feedType === 'home') { - return this.rootStore.api.app.bsky.feed.getTimeline( - params as GetTimeline.QueryParams, - ) + return this.rootStore.agent.getTimeline(params as GetTimeline.QueryParams) } else if (this.feedType === 'goodstuff') { const res = await getGoodStuff( this.rootStore.session.currentSession?.accessJwt || '', @@ -624,7 +589,7 @@ export class FeedModel { ) return res } else { - return this.rootStore.api.app.bsky.feed.getAuthorFeed( + return this.rootStore.agent.getAuthorFeed( params as GetAuthorFeed.QueryParams, ) } diff --git a/src/state/models/votes-view.ts b/src/state/models/likes-view.ts index ad8698d21..5f9df692e 100644 --- a/src/state/models/votes-view.ts +++ b/src/state/models/likes-view.ts @@ -1,6 +1,6 @@ import {makeAutoObservable, runInAction} from 'mobx' import {AtUri} from '../../third-party/uri' -import {AppBskyFeedGetVotes as GetVotes} from '@atproto/api' +import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {RootStoreModel} from './root-store' import {cleanError} from 'lib/strings/errors' import {bundleAsync} from 'lib/async/bundle' @@ -8,24 +8,24 @@ import * as apilib from 'lib/api/index' const PAGE_SIZE = 30 -export type VoteItem = GetVotes.Vote +export type LikeItem = GetLikes.Like -export class VotesViewModel { +export class LikesViewModel { // state isLoading = false isRefreshing = false hasLoaded = false error = '' resolvedUri = '' - params: GetVotes.QueryParams + params: GetLikes.QueryParams hasMore = true loadMoreCursor?: string // data uri: string = '' - votes: VoteItem[] = [] + likes: LikeItem[] = [] - constructor(public rootStore: RootStoreModel, params: GetVotes.QueryParams) { + constructor(public rootStore: RootStoreModel, params: GetLikes.QueryParams) { makeAutoObservable( this, { @@ -68,9 +68,9 @@ export class VotesViewModel { const params = Object.assign({}, this.params, { uri: this.resolvedUri, limit: PAGE_SIZE, - before: replace ? undefined : this.loadMoreCursor, + cursor: replace ? undefined : this.loadMoreCursor, }) - const res = await this.rootStore.api.app.bsky.feed.getVotes(params) + const res = await this.rootStore.agent.getLikes(params) if (replace) { this._replaceAll(res) } else { @@ -85,13 +85,13 @@ export class VotesViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -104,7 +104,7 @@ export class VotesViewModel { // helper functions // = - private async _resolveUri() { + async _resolveUri() { const urip = new AtUri(this.params.uri) if (!urip.host.startsWith('did:')) { try { @@ -118,14 +118,14 @@ export class VotesViewModel { }) } - private _replaceAll(res: GetVotes.Response) { - this.votes = [] + _replaceAll(res: GetLikes.Response) { + this.likes = [] this._appendAll(res) } - private _appendAll(res: GetVotes.Response) { + _appendAll(res: GetLikes.Response) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor - this.votes = this.votes.concat(res.data.votes) + this.likes = this.likes.concat(res.data.likes) } } diff --git a/src/state/models/log.ts b/src/state/models/log.ts index ed701dc61..d80617139 100644 --- a/src/state/models/log.ts +++ b/src/state/models/log.ts @@ -1,5 +1,5 @@ import {makeAutoObservable} from 'mobx' -import {XRPCError, XRPCInvalidResponseError} from '@atproto/xrpc' +// import {XRPCError, XRPCInvalidResponseError} from '@atproto/xrpc' TODO const MAX_ENTRIES = 300 @@ -32,7 +32,7 @@ export class LogModel { makeAutoObservable(this) } - private add(entry: LogEntry) { + add(entry: LogEntry) { this.entries.push(entry) while (this.entries.length > MAX_ENTRIES) { this.entries = this.entries.slice(50) @@ -79,14 +79,14 @@ export class LogModel { function detailsToStr(details?: any) { if (details && typeof details !== 'string') { if ( - details instanceof XRPCInvalidResponseError || + // details instanceof XRPCInvalidResponseError || TODO details.constructor.name === 'XRPCInvalidResponseError' ) { return `The server gave an ill-formatted response.\nMethod: ${ details.lexiconNsid }.\nError: ${details.validationError.toString()}` } else if ( - details instanceof XRPCError || + // details instanceof XRPCError || TODO details.constructor.name === 'XRPCError' ) { return `An XRPC error occurred.\nStatus: ${details.status}\nError: ${details.error}\nMessage: ${details.message}` diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 120749155..5f670b8f9 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -85,7 +85,7 @@ export class MeModel { if (sess.hasSession) { this.did = sess.currentSession?.did || '' this.handle = sess.currentSession?.handle || '' - const profile = await this.rootStore.api.app.bsky.actor.getProfile({ + const profile = await this.rootStore.agent.getProfile({ actor: this.did, }) runInAction(() => { diff --git a/src/state/models/notifications-view.ts b/src/state/models/notifications-view.ts index e88af590b..4f7a52fd9 100644 --- a/src/state/models/notifications-view.ts +++ b/src/state/models/notifications-view.ts @@ -1,11 +1,10 @@ import {makeAutoObservable, runInAction} from 'mobx' import { - AppBskyNotificationList as ListNotifications, - AppBskyActorRef as ActorRef, + AppBskyNotificationListNotifications as ListNotifications, + AppBskyActorDefs, AppBskyFeedPost, AppBskyFeedRepost, - AppBskyFeedVote, - AppBskyGraphAssertion, + AppBskyFeedLike, AppBskyGraphFollow, } from '@atproto/api' import AwaitLock from 'await-lock' @@ -28,8 +27,7 @@ export interface GroupedNotification extends ListNotifications.Notification { type SupportedRecord = | AppBskyFeedPost.Record | AppBskyFeedRepost.Record - | AppBskyFeedVote.Record - | AppBskyGraphAssertion.Record + | AppBskyFeedLike.Record | AppBskyGraphFollow.Record export class NotificationsViewItemModel { @@ -39,11 +37,10 @@ export class NotificationsViewItemModel { // data uri: string = '' cid: string = '' - author: ActorRef.WithInfo = { + author: AppBskyActorDefs.ProfileViewBasic = { did: '', handle: '', avatar: '', - declaration: {cid: '', actorType: ''}, } reason: string = '' reasonSubject?: string @@ -86,8 +83,8 @@ export class NotificationsViewItemModel { } } - get isUpvote() { - return this.reason === 'vote' + get isLike() { + return this.reason === 'like' } get isRepost() { @@ -102,16 +99,22 @@ export class NotificationsViewItemModel { return this.reason === 'reply' } - get isFollow() { - return this.reason === 'follow' + get isQuote() { + return this.reason === 'quote' } - get isAssertion() { - return this.reason === 'assertion' + get isFollow() { + return this.reason === 'follow' } get needsAdditionalData() { - if (this.isUpvote || this.isRepost || this.isReply || this.isMention) { + if ( + this.isLike || + this.isRepost || + this.isReply || + this.isQuote || + this.isMention + ) { return !this.additionalPost } return false @@ -124,7 +127,7 @@ export class NotificationsViewItemModel { const record = this.record if ( AppBskyFeedRepost.isRecord(record) || - AppBskyFeedVote.isRecord(record) + AppBskyFeedLike.isRecord(record) ) { return record.subject.uri } @@ -135,8 +138,7 @@ export class NotificationsViewItemModel { for (const ns of [ AppBskyFeedPost, AppBskyFeedRepost, - AppBskyFeedVote, - AppBskyGraphAssertion, + AppBskyFeedLike, AppBskyGraphFollow, ]) { if (ns.isRecord(v)) { @@ -163,9 +165,9 @@ export class NotificationsViewItemModel { return } let postUri - if (this.isReply || this.isMention) { + if (this.isReply || this.isQuote || this.isMention) { postUri = this.uri - } else if (this.isUpvote || this.isRepost) { + } else if (this.isLike || this.isRepost) { postUri = this.subjectUri } if (postUri) { @@ -194,7 +196,7 @@ export class NotificationsViewModel { loadMoreCursor?: string // used to linearize async modifications to state - private lock = new AwaitLock() + lock = new AwaitLock() // data notifications: NotificationsViewItemModel[] = [] @@ -266,7 +268,7 @@ export class NotificationsViewModel { const params = Object.assign({}, this.params, { limit: PAGE_SIZE, }) - const res = await this.rootStore.api.app.bsky.notification.list(params) + const res = await this.rootStore.agent.listNotifications(params) await this._replaceAll(res) this._xIdle() } catch (e: any) { @@ -297,9 +299,9 @@ export class NotificationsViewModel { try { const params = Object.assign({}, this.params, { limit: PAGE_SIZE, - before: this.loadMoreCursor, + cursor: this.loadMoreCursor, }) - const res = await this.rootStore.api.app.bsky.notification.list(params) + const res = await this.rootStore.agent.listNotifications(params) await this._appendAll(res) this._xIdle() } catch (e: any) { @@ -325,7 +327,7 @@ export class NotificationsViewModel { try { this._xLoading() try { - const res = await this.rootStore.api.app.bsky.notification.list({ + const res = await this.rootStore.agent.listNotifications({ limit: PAGE_SIZE, }) await this._prependAll(res) @@ -357,8 +359,8 @@ export class NotificationsViewModel { try { do { const res: ListNotifications.Response = - await this.rootStore.api.app.bsky.notification.list({ - before: cursor, + await this.rootStore.agent.listNotifications({ + cursor, limit: Math.min(numToFetch, 100), }) if (res.data.notifications.length === 0) { @@ -390,7 +392,7 @@ export class NotificationsViewModel { */ loadUnreadCount = bundleAsync(async () => { const old = this.unreadCount - const res = await this.rootStore.api.app.bsky.notification.getCount() + const res = await this.rootStore.agent.countUnreadNotifications() runInAction(() => { this.unreadCount = res.data.count }) @@ -408,9 +410,7 @@ export class NotificationsViewModel { for (const notif of this.notifications) { notif.isRead = true } - await this.rootStore.api.app.bsky.notification.updateSeen({ - seenAt: new Date().toISOString(), - }) + await this.rootStore.agent.updateSeenNotifications() } catch (e: any) { this.rootStore.log.warn('Failed to update notifications read state', e) } @@ -418,7 +418,7 @@ export class NotificationsViewModel { async getNewMostRecent(): Promise<NotificationsViewItemModel | undefined> { let old = this.mostRecentNotificationUri - const res = await this.rootStore.api.app.bsky.notification.list({ + const res = await this.rootStore.agent.listNotifications({ limit: 1, }) if (!res.data.notifications[0] || old === res.data.notifications[0].uri) { @@ -437,13 +437,13 @@ export class NotificationsViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -456,14 +456,14 @@ export class NotificationsViewModel { // helper functions // = - private async _replaceAll(res: ListNotifications.Response) { + async _replaceAll(res: ListNotifications.Response) { if (res.data.notifications[0]) { this.mostRecentNotificationUri = res.data.notifications[0].uri } return this._appendAll(res, true) } - private async _appendAll(res: ListNotifications.Response, replace = false) { + async _appendAll(res: ListNotifications.Response, replace = false) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor const promises = [] @@ -494,7 +494,7 @@ export class NotificationsViewModel { }) } - private async _prependAll(res: ListNotifications.Response) { + async _prependAll(res: ListNotifications.Response) { const promises = [] const itemModels: NotificationsViewItemModel[] = [] const dedupedNotifs = res.data.notifications.filter( @@ -525,7 +525,7 @@ export class NotificationsViewModel { }) } - private _updateAll(res: ListNotifications.Response) { + _updateAll(res: ListNotifications.Response) { for (const item of res.data.notifications) { const existingItem = this.notifications.find(item2 => isEq(item, item2)) if (existingItem) { diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts index d58ee691b..c5395b9c8 100644 --- a/src/state/models/post-thread-view.ts +++ b/src/state/models/post-thread-view.ts @@ -2,12 +2,13 @@ import {makeAutoObservable, runInAction} from 'mobx' import { AppBskyFeedGetPostThread as GetPostThread, AppBskyFeedPost as FeedPost, + AppBskyFeedDefs, + RichText, } from '@atproto/api' import {AtUri} from '../../third-party/uri' import {RootStoreModel} from './root-store' import * as apilib from 'lib/api/index' import {cleanError} from 'lib/strings/errors' -import {RichText} from 'lib/strings/rich-text' function* reactKeyGenerator(): Generator<string> { let counter = 0 @@ -26,10 +27,10 @@ export class PostThreadViewPostModel { _hasMore = false // data - post: FeedPost.View + post: AppBskyFeedDefs.PostView postRecord?: FeedPost.Record - parent?: PostThreadViewPostModel | GetPostThread.NotFoundPost - replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[] + parent?: PostThreadViewPostModel | AppBskyFeedDefs.NotFoundPost + replies?: (PostThreadViewPostModel | AppBskyFeedDefs.NotFoundPost)[] richText?: RichText get uri() { @@ -43,7 +44,7 @@ export class PostThreadViewPostModel { constructor( public rootStore: RootStoreModel, reactKey: string, - v: GetPostThread.ThreadViewPost, + v: AppBskyFeedDefs.ThreadViewPost, ) { this._reactKey = reactKey this.post = v.post @@ -51,11 +52,7 @@ export class PostThreadViewPostModel { const valid = FeedPost.validateRecord(this.post.record) if (valid.success) { this.postRecord = this.post.record - this.richText = new RichText( - this.postRecord.text, - this.postRecord.entities, - {cleanNewlines: true}, - ) + this.richText = new RichText(this.postRecord, {cleanNewlines: true}) } else { rootStore.log.warn( 'Received an invalid app.bsky.feed.post record', @@ -74,14 +71,14 @@ export class PostThreadViewPostModel { assignTreeModels( keyGen: Generator<string>, - v: GetPostThread.ThreadViewPost, + v: AppBskyFeedDefs.ThreadViewPost, higlightedPostUri: string, includeParent = true, includeChildren = true, ) { // parents if (includeParent && v.parent) { - if (GetPostThread.isThreadViewPost(v.parent)) { + if (AppBskyFeedDefs.isThreadViewPost(v.parent)) { const parentModel = new PostThreadViewPostModel( this.rootStore, keyGen.next().value, @@ -100,7 +97,7 @@ export class PostThreadViewPostModel { ) } this.parent = parentModel - } else if (GetPostThread.isNotFoundPost(v.parent)) { + } else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) { this.parent = v.parent } } @@ -108,7 +105,7 @@ export class PostThreadViewPostModel { if (includeChildren && v.replies) { const replies = [] for (const item of v.replies) { - if (GetPostThread.isThreadViewPost(item)) { + if (AppBskyFeedDefs.isThreadViewPost(item)) { const itemModel = new PostThreadViewPostModel( this.rootStore, keyGen.next().value, @@ -128,7 +125,7 @@ export class PostThreadViewPostModel { ) } replies.push(itemModel) - } else if (GetPostThread.isNotFoundPost(item)) { + } else if (AppBskyFeedDefs.isNotFoundPost(item)) { replies.push(item) } } @@ -136,68 +133,43 @@ export class PostThreadViewPostModel { } } - async toggleUpvote() { - const wasUpvoted = !!this.post.viewer.upvote - const wasDownvoted = !!this.post.viewer.downvote - const res = await this.rootStore.api.app.bsky.feed.setVote({ - subject: { - uri: this.post.uri, - cid: this.post.cid, - }, - direction: wasUpvoted ? 'none' : 'up', - }) - runInAction(() => { - if (wasDownvoted) { - this.post.downvoteCount-- - } - if (wasUpvoted) { - this.post.upvoteCount-- - } else { - this.post.upvoteCount++ - } - this.post.viewer.upvote = res.data.upvote - this.post.viewer.downvote = res.data.downvote - }) - } - - async toggleDownvote() { - const wasUpvoted = !!this.post.viewer.upvote - const wasDownvoted = !!this.post.viewer.downvote - const res = await this.rootStore.api.app.bsky.feed.setVote({ - subject: { - uri: this.post.uri, - cid: this.post.cid, - }, - direction: wasDownvoted ? 'none' : 'down', - }) - runInAction(() => { - if (wasUpvoted) { - this.post.upvoteCount-- - } - if (wasDownvoted) { - this.post.downvoteCount-- - } else { - this.post.downvoteCount++ - } - this.post.viewer.upvote = res.data.upvote - this.post.viewer.downvote = res.data.downvote - }) + async toggleLike() { + if (this.post.viewer?.like) { + await this.rootStore.agent.deleteLike(this.post.viewer.like) + runInAction(() => { + this.post.likeCount = this.post.likeCount || 0 + this.post.viewer = this.post.viewer || {} + this.post.likeCount-- + this.post.viewer.like = undefined + }) + } else { + const res = await this.rootStore.agent.like(this.post.uri, this.post.cid) + runInAction(() => { + this.post.likeCount = this.post.likeCount || 0 + this.post.viewer = this.post.viewer || {} + this.post.likeCount++ + this.post.viewer.like = res.uri + }) + } } async toggleRepost() { - if (this.post.viewer.repost) { - await apilib.unrepost(this.rootStore, this.post.viewer.repost) + if (this.post.viewer?.repost) { + await this.rootStore.agent.deleteRepost(this.post.viewer.repost) runInAction(() => { + this.post.repostCount = this.post.repostCount || 0 + this.post.viewer = this.post.viewer || {} this.post.repostCount-- this.post.viewer.repost = undefined }) } else { - const res = await apilib.repost( - this.rootStore, + const res = await this.rootStore.agent.repost( this.post.uri, this.post.cid, ) runInAction(() => { + this.post.repostCount = this.post.repostCount || 0 + this.post.viewer = this.post.viewer || {} this.post.repostCount++ this.post.viewer.repost = res.uri }) @@ -205,10 +177,7 @@ export class PostThreadViewPostModel { } async delete() { - await this.rootStore.api.app.bsky.feed.post.delete({ - did: this.post.author.did, - rkey: new AtUri(this.post.uri).rkey, - }) + await this.rootStore.agent.deletePost(this.post.uri) this.rootStore.emitPostDeleted(this.post.uri) } } @@ -301,14 +270,14 @@ export class PostThreadViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' this.notFound = false } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -322,7 +291,7 @@ export class PostThreadViewModel { // loader functions // = - private async _resolveUri() { + async _resolveUri() { const urip = new AtUri(this.params.uri) if (!urip.host.startsWith('did:')) { try { @@ -336,10 +305,10 @@ export class PostThreadViewModel { }) } - private async _load(isRefreshing = false) { + async _load(isRefreshing = false) { this._xLoading(isRefreshing) try { - const res = await this.rootStore.api.app.bsky.feed.getPostThread( + const res = await this.rootStore.agent.getPostThread( Object.assign({}, this.params, {uri: this.resolvedUri}), ) this._replaceAll(res) @@ -349,18 +318,18 @@ export class PostThreadViewModel { } } - private _replaceAll(res: GetPostThread.Response) { + _replaceAll(res: GetPostThread.Response) { sortThread(res.data.thread) const keyGen = reactKeyGenerator() const thread = new PostThreadViewPostModel( this.rootStore, keyGen.next().value, - res.data.thread as GetPostThread.ThreadViewPost, + res.data.thread as AppBskyFeedDefs.ThreadViewPost, ) thread._isHighlightedPost = true thread.assignTreeModels( keyGen, - res.data.thread as GetPostThread.ThreadViewPost, + res.data.thread as AppBskyFeedDefs.ThreadViewPost, thread.uri, ) this.thread = thread @@ -368,25 +337,25 @@ export class PostThreadViewModel { } type MaybePost = - | GetPostThread.ThreadViewPost - | GetPostThread.NotFoundPost + | AppBskyFeedDefs.ThreadViewPost + | AppBskyFeedDefs.NotFoundPost | {[k: string]: unknown; $type: string} function sortThread(post: MaybePost) { if (post.notFound) { return } - post = post as GetPostThread.ThreadViewPost + post = post as AppBskyFeedDefs.ThreadViewPost if (post.replies) { post.replies.sort((a: MaybePost, b: MaybePost) => { - post = post as GetPostThread.ThreadViewPost + post = post as AppBskyFeedDefs.ThreadViewPost if (a.notFound) { return 1 } if (b.notFound) { return -1 } - a = a as GetPostThread.ThreadViewPost - b = b as GetPostThread.ThreadViewPost + a = a as AppBskyFeedDefs.ThreadViewPost + b = b as AppBskyFeedDefs.ThreadViewPost const aIsByOp = a.post.author.did === post.post.author.did const bIsByOp = b.post.author.did === post.post.author.did if (aIsByOp && bIsByOp) { diff --git a/src/state/models/post.ts b/src/state/models/post.ts index 749e98bb0..c7f2896ba 100644 --- a/src/state/models/post.ts +++ b/src/state/models/post.ts @@ -58,12 +58,12 @@ export class PostModel implements RemoveIndex<Post.Record> { // state transitions // = - private _xLoading() { + _xLoading() { this.isLoading = true this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.hasLoaded = true this.error = cleanError(err) @@ -75,12 +75,12 @@ export class PostModel implements RemoveIndex<Post.Record> { // loader functions // = - private async _load() { + async _load() { this._xLoading() try { const urip = new AtUri(this.uri) - const res = await this.rootStore.api.app.bsky.feed.post.get({ - user: urip.host, + const res = await this.rootStore.agent.getPost({ + repo: urip.host, rkey: urip.rkey, }) // TODO @@ -94,7 +94,7 @@ export class PostModel implements RemoveIndex<Post.Record> { } } - private _replaceAll(res: Post.Record) { + _replaceAll(res: Post.Record) { this.text = res.text this.entities = res.entities this.reply = res.reply diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts index 9d3eeff58..eacc6a298 100644 --- a/src/state/models/profile-view.ts +++ b/src/state/models/profile-view.ts @@ -2,15 +2,12 @@ import {makeAutoObservable, runInAction} from 'mobx' import {PickedMedia} from 'lib/media/picker' import { AppBskyActorGetProfile as GetProfile, - AppBskySystemDeclRef, - AppBskyActorUpdateProfile, + AppBskyActorProfile, + RichText, } from '@atproto/api' -type DeclRef = AppBskySystemDeclRef.Main -import {extractEntities} from 'lib/strings/rich-text-detection' import {RootStoreModel} from './root-store' import * as apilib from 'lib/api/index' import {cleanError} from 'lib/strings/errors' -import {RichText} from 'lib/strings/rich-text' export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser' @@ -35,22 +32,18 @@ export class ProfileViewModel { // data did: string = '' handle: string = '' - declaration: DeclRef = { - cid: '', - actorType: '', - } creator: string = '' - displayName?: string - description?: string - avatar?: string - banner?: string + displayName?: string = '' + description?: string = '' + avatar?: string = '' + banner?: string = '' followersCount: number = 0 followsCount: number = 0 postsCount: number = 0 viewer = new ProfileViewViewerModel() // added data - descriptionRichText?: RichText + descriptionRichText?: RichText = new RichText({text: ''}) constructor( public rootStore: RootStoreModel, @@ -79,10 +72,6 @@ export class ProfileViewModel { return this.hasLoaded && !this.hasContent } - get isUser() { - return this.declaration.actorType === ACTOR_TYPE_USER - } - // public api // = @@ -111,18 +100,14 @@ export class ProfileViewModel { } if (followUri) { - await apilib.unfollow(this.rootStore, followUri) + await this.rootStore.agent.deleteFollow(followUri) runInAction(() => { this.followersCount-- this.viewer.following = undefined this.rootStore.me.follows.removeFollow(this.did) }) } else { - const res = await apilib.follow( - this.rootStore, - this.did, - this.declaration.cid, - ) + const res = await this.rootStore.agent.follow(this.did) runInAction(() => { this.followersCount++ this.viewer.following = res.uri @@ -132,49 +117,48 @@ export class ProfileViewModel { } async updateProfile( - updates: AppBskyActorUpdateProfile.InputSchema, + updates: AppBskyActorProfile.Record, newUserAvatar: PickedMedia | undefined | null, newUserBanner: PickedMedia | undefined | null, ) { - if (newUserAvatar) { - const res = await apilib.uploadBlob( - this.rootStore, - newUserAvatar.path, - newUserAvatar.mime, - ) - updates.avatar = { - cid: res.data.cid, - mimeType: newUserAvatar.mime, + await this.rootStore.agent.upsertProfile(async existing => { + existing = existing || {} + existing.displayName = updates.displayName + existing.description = updates.description + if (newUserAvatar) { + const res = await apilib.uploadBlob( + this.rootStore, + newUserAvatar.path, + newUserAvatar.mime, + ) + existing.avatar = res.data.blob + } else if (newUserAvatar === null) { + existing.avatar = undefined } - } else if (newUserAvatar === null) { - updates.avatar = null - } - if (newUserBanner) { - const res = await apilib.uploadBlob( - this.rootStore, - newUserBanner.path, - newUserBanner.mime, - ) - updates.banner = { - cid: res.data.cid, - mimeType: newUserBanner.mime, + if (newUserBanner) { + const res = await apilib.uploadBlob( + this.rootStore, + newUserBanner.path, + newUserBanner.mime, + ) + existing.banner = res.data.blob + } else if (newUserBanner === null) { + existing.banner = undefined } - } else if (newUserBanner === null) { - updates.banner = null - } - await this.rootStore.api.app.bsky.actor.updateProfile(updates) + return existing + }) await this.rootStore.me.load() await this.refresh() } async muteAccount() { - await this.rootStore.api.app.bsky.graph.mute({user: this.did}) + await this.rootStore.agent.mute(this.did) this.viewer.muted = true await this.refresh() } async unmuteAccount() { - await this.rootStore.api.app.bsky.graph.unmute({user: this.did}) + await this.rootStore.agent.unmute(this.did) this.viewer.muted = false await this.refresh() } @@ -182,13 +166,13 @@ export class ProfileViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -201,40 +185,40 @@ export class ProfileViewModel { // loader functions // = - private async _load(isRefreshing = false) { + async _load(isRefreshing = false) { this._xLoading(isRefreshing) try { - const res = await this.rootStore.api.app.bsky.actor.getProfile( - this.params, - ) + const res = await this.rootStore.agent.getProfile(this.params) this.rootStore.profiles.overwrite(this.params.actor, res) // cache invalidation this._replaceAll(res) + await this._createRichText() this._xIdle() } catch (e: any) { this._xIdle(e) } } - private _replaceAll(res: GetProfile.Response) { + _replaceAll(res: GetProfile.Response) { this.did = res.data.did this.handle = res.data.handle - Object.assign(this.declaration, res.data.declaration) - this.creator = res.data.creator this.displayName = res.data.displayName this.description = res.data.description this.avatar = res.data.avatar this.banner = res.data.banner - this.followersCount = res.data.followersCount - this.followsCount = res.data.followsCount - this.postsCount = res.data.postsCount + this.followersCount = res.data.followersCount || 0 + this.followsCount = res.data.followsCount || 0 + this.postsCount = res.data.postsCount || 0 if (res.data.viewer) { Object.assign(this.viewer, res.data.viewer) this.rootStore.me.follows.hydrate(this.did, res.data.viewer.following) } + } + + async _createRichText() { this.descriptionRichText = new RichText( - this.description || '', - extractEntities(this.description || ''), + {text: this.description || ''}, {cleanNewlines: true}, ) + await this.descriptionRichText.detectFacets(this.rootStore.agent) } } diff --git a/src/state/models/profiles-view.ts b/src/state/models/profiles-view.ts index 4241e50e1..30e6d0442 100644 --- a/src/state/models/profiles-view.ts +++ b/src/state/models/profiles-view.ts @@ -31,7 +31,7 @@ export class ProfilesViewModel { } } try { - const promise = this.rootStore.api.app.bsky.actor.getProfile({ + const promise = this.rootStore.agent.getProfile({ actor: did, }) this.cache.set(did, promise) diff --git a/src/state/models/reposted-by-view.ts b/src/state/models/reposted-by-view.ts index 69a728d6f..c9b089c70 100644 --- a/src/state/models/reposted-by-view.ts +++ b/src/state/models/reposted-by-view.ts @@ -2,7 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {AtUri} from '../../third-party/uri' import { AppBskyFeedGetRepostedBy as GetRepostedBy, - AppBskyActorRef as ActorRef, + AppBskyActorDefs, } from '@atproto/api' import {RootStoreModel} from './root-store' import {bundleAsync} from 'lib/async/bundle' @@ -11,7 +11,7 @@ import * as apilib from 'lib/api/index' const PAGE_SIZE = 30 -export type RepostedByItem = ActorRef.WithInfo +export type RepostedByItem = AppBskyActorDefs.ProfileViewBasic export class RepostedByViewModel { // state @@ -71,9 +71,9 @@ export class RepostedByViewModel { const params = Object.assign({}, this.params, { uri: this.resolvedUri, limit: PAGE_SIZE, - before: replace ? undefined : this.loadMoreCursor, + cursor: replace ? undefined : this.loadMoreCursor, }) - const res = await this.rootStore.api.app.bsky.feed.getRepostedBy(params) + const res = await this.rootStore.agent.getRepostedBy(params) if (replace) { this._replaceAll(res) } else { @@ -88,13 +88,13 @@ export class RepostedByViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -107,7 +107,7 @@ export class RepostedByViewModel { // helper functions // = - private async _resolveUri() { + async _resolveUri() { const urip = new AtUri(this.params.uri) if (!urip.host.startsWith('did:')) { try { @@ -121,12 +121,12 @@ export class RepostedByViewModel { }) } - private _replaceAll(res: GetRepostedBy.Response) { + _replaceAll(res: GetRepostedBy.Response) { this.repostedBy = [] this._appendAll(res) } - private _appendAll(res: GetRepostedBy.Response) { + _appendAll(res: GetRepostedBy.Response) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor this.repostedBy = this.repostedBy.concat(res.data.repostedBy) diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index d8336d005..0c2a31d28 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -2,8 +2,8 @@ * The root store is the base of all modeled state. */ -import {makeAutoObservable, runInAction} from 'mobx' -import {AtpAgent} from '@atproto/api' +import {makeAutoObservable} from 'mobx' +import {BskyAgent} from '@atproto/api' import {createContext, useContext} from 'react' import {DeviceEventEmitter, EmitterSubscription} from 'react-native' import * as BgScheduler from 'lib/bg-scheduler' @@ -29,7 +29,7 @@ export const appInfo = z.object({ export type AppInfo = z.infer<typeof appInfo> export class RootStoreModel { - agent: AtpAgent + agent: BskyAgent appInfo?: AppInfo log = new LogModel() session = new SessionModel(this) @@ -40,41 +40,16 @@ export class RootStoreModel { linkMetas = new LinkMetasCache(this) imageSizes = new ImageSizesCache() - // HACK - // this flag is to track the lexicon breaking refactor - // it should be removed once we get that done - // -prf - hackUpgradeNeeded = false - async hackCheckIfUpgradeNeeded() { - try { - this.log.debug('hackCheckIfUpgradeNeeded()') - const res = await fetch('https://bsky.social/xrpc/app.bsky.feed.getLikes') - await res.text() - runInAction(() => { - this.hackUpgradeNeeded = res.status !== 501 - this.log.debug( - `hackCheckIfUpgradeNeeded() said ${this.hackUpgradeNeeded}`, - ) - }) - } catch (e) { - this.log.error('Failed to hackCheckIfUpgradeNeeded', {e}) - } - } - - constructor(agent: AtpAgent) { + constructor(agent: BskyAgent) { this.agent = agent makeAutoObservable(this, { - api: false, + agent: false, serialize: false, hydrate: false, }) this.initBgFetch() } - get api() { - return this.agent.api - } - setAppInfo(info: AppInfo) { this.appInfo = info } @@ -131,7 +106,7 @@ export class RootStoreModel { /** * Called by the session model. Refreshes session-oriented state. */ - async handleSessionChange(agent: AtpAgent) { + async handleSessionChange(agent: BskyAgent) { this.log.debug('RootStoreModel:handleSessionChange') this.agent = agent this.me.clear() @@ -259,7 +234,7 @@ export class RootStoreModel { async onBgFetch(taskId: string) { this.log.debug(`Background fetch fired for task ${taskId}`) if (this.session.hasSession) { - const res = await this.api.app.bsky.notification.getCount() + const res = await this.agent.countUnreadNotifications() const hasNewNotifs = this.me.notifications.unreadCount !== res.data.count this.emitUnreadNotifications(res.data.count) this.log.debug( @@ -286,7 +261,7 @@ export class RootStoreModel { } const throwawayInst = new RootStoreModel( - new AtpAgent({service: 'http://localhost'}), + new BskyAgent({service: 'http://localhost'}), ) // this will be replaced by the loader, we just need to supply a value at init const RootStoreContext = createContext<RootStoreModel>(throwawayInst) export const RootStoreProvider = RootStoreContext.Provider diff --git a/src/state/models/session.ts b/src/state/models/session.ts index e131b2b2c..c2e10880d 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -1,9 +1,9 @@ import {makeAutoObservable, runInAction} from 'mobx' import { - AtpAgent, + BskyAgent, AtpSessionEvent, AtpSessionData, - ComAtprotoServerGetAccountsConfig as GetAccountsConfig, + ComAtprotoServerDescribeServer as DescribeServer, } from '@atproto/api' import normalizeUrl from 'normalize-url' import {isObj, hasProp} from 'lib/type-guards' @@ -11,7 +11,7 @@ import {networkRetry} from 'lib/async/retry' import {z} from 'zod' import {RootStoreModel} from './root-store' -export type ServiceDescription = GetAccountsConfig.OutputSchema +export type ServiceDescription = DescribeServer.OutputSchema export const activeSession = z.object({ service: z.string(), @@ -40,7 +40,7 @@ export class SessionModel { // emergency log facility to help us track down this logout issue // remove when resolved // -prf - private _log(message: string, details?: Record<string, any>) { + _log(message: string, details?: Record<string, any>) { details = details || {} details.state = { data: this.data, @@ -73,6 +73,7 @@ export class SessionModel { rootStore: false, serialize: false, hydrate: false, + hasSession: false, }) } @@ -154,7 +155,7 @@ export class SessionModel { /** * Sets the active session */ - async setActiveSession(agent: AtpAgent, did: string) { + async setActiveSession(agent: BskyAgent, did: string) { this._log('SessionModel:setActiveSession') this.data = { service: agent.service.toString(), @@ -166,7 +167,7 @@ export class SessionModel { /** * Upserts a session into the accounts */ - private persistSession( + persistSession( service: string, did: string, event: AtpSessionEvent, @@ -225,7 +226,7 @@ export class SessionModel { /** * Clears any session tokens from the accounts; used on logout. */ - private clearSessionTokens() { + clearSessionTokens() { this._log('SessionModel:clearSessionTokens') this.accounts = this.accounts.map(acct => ({ service: acct.service, @@ -239,10 +240,8 @@ export class SessionModel { /** * Fetches additional information about an account on load. */ - private async loadAccountInfo(agent: AtpAgent, did: string) { - const res = await agent.api.app.bsky.actor - .getProfile({actor: did}) - .catch(_e => undefined) + async loadAccountInfo(agent: BskyAgent, did: string) { + const res = await agent.getProfile({actor: did}).catch(_e => undefined) if (res) { return { dispayName: res.data.displayName, @@ -255,8 +254,8 @@ export class SessionModel { * Helper to fetch the accounts config settings from an account. */ async describeService(service: string): Promise<ServiceDescription> { - const agent = new AtpAgent({service}) - const res = await agent.api.com.atproto.server.getAccountsConfig({}) + const agent = new BskyAgent({service}) + const res = await agent.com.atproto.server.describeServer({}) return res.data } @@ -272,7 +271,7 @@ export class SessionModel { return false } - const agent = new AtpAgent({ + const agent = new BskyAgent({ service: account.service, persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) => { this.persistSession(account.service, account.did, evt, sess) @@ -321,7 +320,7 @@ export class SessionModel { password: string }) { this._log('SessionModel:login') - const agent = new AtpAgent({service}) + const agent = new BskyAgent({service}) await agent.login({identifier, password}) if (!agent.session) { throw new Error('Failed to establish session') @@ -355,7 +354,7 @@ export class SessionModel { inviteCode?: string }) { this._log('SessionModel:createAccount') - const agent = new AtpAgent({service}) + const agent = new BskyAgent({service}) await agent.createAccount({ handle, password, @@ -389,7 +388,7 @@ export class SessionModel { // need to evaluate why deleting the session has caused errors at times // -prf /*if (this.hasSession) { - this.rootStore.api.com.atproto.session.delete().catch((e: any) => { + this.rootStore.agent.com.atproto.session.delete().catch((e: any) => { this.rootStore.log.warn( '(Minor issue) Failed to delete session on the server', e, @@ -415,7 +414,7 @@ export class SessionModel { if (!sess) { return } - const res = await this.rootStore.api.app.bsky.actor + const res = await this.rootStore.agent .getProfile({actor: sess.did}) .catch(_e => undefined) if (res?.success) { diff --git a/src/state/models/suggested-posts-view.ts b/src/state/models/suggested-posts-view.ts index 7a5ca81b9..46bf235ff 100644 --- a/src/state/models/suggested-posts-view.ts +++ b/src/state/models/suggested-posts-view.ts @@ -72,12 +72,12 @@ export class SuggestedPostsView { // state transitions // = - private _xLoading() { + _xLoading() { this.isLoading = true this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.hasLoaded = true this.error = cleanError(err) diff --git a/src/state/models/ui/create-account.ts b/src/state/models/ui/create-account.ts index a212fe05e..e661cb59d 100644 --- a/src/state/models/ui/create-account.ts +++ b/src/state/models/ui/create-account.ts @@ -2,7 +2,7 @@ import {makeAutoObservable} from 'mobx' import {RootStoreModel} from '../root-store' import {ServiceDescription} from '../session' import {DEFAULT_SERVICE} from 'state/index' -import {ComAtprotoAccountCreate} from '@atproto/api' +import {ComAtprotoServerCreateAccount} from '@atproto/api' import * as EmailValidator from 'email-validator' import {createFullHandle} from 'lib/strings/handles' import {cleanError} from 'lib/strings/errors' @@ -99,7 +99,7 @@ export class CreateAccountModel { }) } catch (e: any) { let errMsg = e.toString() - if (e instanceof ComAtprotoAccountCreate.InvalidInviteCodeError) { + if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) { errMsg = 'Invite code not accepted. Check that you input it correctly and try again.' } diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts index 280541b74..59529aa39 100644 --- a/src/state/models/ui/profile.ts +++ b/src/state/models/ui/profile.ts @@ -40,7 +40,7 @@ export class ProfileUiModel { ) this.profile = new ProfileViewModel(rootStore, {actor: params.user}) this.feed = new FeedModel(rootStore, 'author', { - author: params.user, + actor: params.user, limit: 10, }) } @@ -64,16 +64,8 @@ export class ProfileUiModel { return this.profile.isRefreshing || this.currentView.isRefreshing } - get isUser() { - return this.profile.isUser - } - get selectorItems() { - if (this.isUser) { - return USER_SELECTOR_ITEMS - } else { - return USER_SELECTOR_ITEMS - } + return USER_SELECTOR_ITEMS } get selectedView() { diff --git a/src/state/models/ui/search.ts b/src/state/models/ui/search.ts index 91e1b24bf..8436b0984 100644 --- a/src/state/models/ui/search.ts +++ b/src/state/models/ui/search.ts @@ -1,6 +1,6 @@ import {makeAutoObservable, runInAction} from 'mobx' import {searchProfiles, searchPosts} from 'lib/api/search' -import {AppBskyActorProfile as Profile} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' export class SearchUIModel { @@ -8,7 +8,7 @@ export class SearchUIModel { isProfilesLoading = false query: string = '' postUris: string[] = [] - profiles: Profile.View[] = [] + profiles: AppBskyActorDefs.ProfileView[] = [] constructor(public rootStore: RootStoreModel) { makeAutoObservable(this) @@ -34,10 +34,10 @@ export class SearchUIModel { this.isPostsLoading = false }) - let profiles: Profile.View[] = [] + let profiles: AppBskyActorDefs.ProfileView[] = [] if (profilesSearch?.length) { do { - const res = await this.rootStore.api.app.bsky.actor.getProfiles({ + const res = await this.rootStore.agent.getProfiles({ actors: profilesSearch.splice(0, 25).map(p => p.did), }) profiles = profiles.concat(res.data.profiles) diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index fec1e2899..7f57d5b54 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -1,3 +1,4 @@ +import {AppBskyEmbedRecord} from '@atproto/api' import {RootStoreModel} from '../root-store' import {makeAutoObservable} from 'mobx' import {ProfileViewModel} from '../profile-view' @@ -111,6 +112,7 @@ export interface ComposerOptsQuote { displayName?: string avatar?: string } + embeds?: AppBskyEmbedRecord.ViewRecord['embeds'] } export interface ComposerOpts { replyTo?: ComposerOptsPostRef diff --git a/src/state/models/user-autocomplete-view.ts b/src/state/models/user-autocomplete-view.ts index 8e4211c27..ad89bb08b 100644 --- a/src/state/models/user-autocomplete-view.ts +++ b/src/state/models/user-autocomplete-view.ts @@ -1,5 +1,5 @@ import {makeAutoObservable, runInAction} from 'mobx' -import {AppBskyActorRef} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import AwaitLock from 'await-lock' import {RootStoreModel} from './root-store' @@ -11,8 +11,8 @@ export class UserAutocompleteViewModel { lock = new AwaitLock() // data - follows: AppBskyActorRef.WithInfo[] = [] - searchRes: AppBskyActorRef.WithInfo[] = [] + follows: AppBskyActorDefs.ProfileViewBasic[] = [] + searchRes: AppBskyActorDefs.ProfileViewBasic[] = [] knownHandles: Set<string> = new Set() constructor(public rootStore: RootStoreModel) { @@ -76,9 +76,9 @@ export class UserAutocompleteViewModel { // internal // = - private async _getFollows() { - const res = await this.rootStore.api.app.bsky.graph.getFollows({ - user: this.rootStore.me.did || '', + async _getFollows() { + const res = await this.rootStore.agent.getFollows({ + actor: this.rootStore.me.did || '', }) runInAction(() => { this.follows = res.data.follows @@ -88,13 +88,13 @@ export class UserAutocompleteViewModel { }) } - private async _search() { - const res = await this.rootStore.api.app.bsky.actor.searchTypeahead({ + async _search() { + const res = await this.rootStore.agent.searchActorsTypeahead({ term: this.prefix, limit: 8, }) runInAction(() => { - this.searchRes = res.data.users + this.searchRes = res.data.actors for (const u of this.searchRes) { this.knownHandles.add(u.handle) } diff --git a/src/state/models/user-followers-view.ts b/src/state/models/user-followers-view.ts index 7400262a4..055032eb7 100644 --- a/src/state/models/user-followers-view.ts +++ b/src/state/models/user-followers-view.ts @@ -1,7 +1,7 @@ import {makeAutoObservable} from 'mobx' import { AppBskyGraphGetFollowers as GetFollowers, - AppBskyActorRef as ActorRef, + AppBskyActorDefs as ActorDefs, } from '@atproto/api' import {RootStoreModel} from './root-store' import {cleanError} from 'lib/strings/errors' @@ -9,7 +9,7 @@ import {bundleAsync} from 'lib/async/bundle' const PAGE_SIZE = 30 -export type FollowerItem = ActorRef.WithInfo +export type FollowerItem = ActorDefs.ProfileViewBasic export class UserFollowersViewModel { // state @@ -22,10 +22,9 @@ export class UserFollowersViewModel { loadMoreCursor?: string // data - subject: ActorRef.WithInfo = { + subject: ActorDefs.ProfileViewBasic = { did: '', handle: '', - declaration: {cid: '', actorType: ''}, } followers: FollowerItem[] = [] @@ -71,9 +70,9 @@ export class UserFollowersViewModel { try { const params = Object.assign({}, this.params, { limit: PAGE_SIZE, - before: replace ? undefined : this.loadMoreCursor, + cursor: replace ? undefined : this.loadMoreCursor, }) - const res = await this.rootStore.api.app.bsky.graph.getFollowers(params) + const res = await this.rootStore.agent.getFollowers(params) if (replace) { this._replaceAll(res) } else { @@ -88,13 +87,13 @@ export class UserFollowersViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -107,12 +106,12 @@ export class UserFollowersViewModel { // helper functions // = - private _replaceAll(res: GetFollowers.Response) { + _replaceAll(res: GetFollowers.Response) { this.followers = [] this._appendAll(res) } - private _appendAll(res: GetFollowers.Response) { + _appendAll(res: GetFollowers.Response) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor this.followers = this.followers.concat(res.data.followers) diff --git a/src/state/models/user-follows-view.ts b/src/state/models/user-follows-view.ts index 7d28d7ebd..6d9d84592 100644 --- a/src/state/models/user-follows-view.ts +++ b/src/state/models/user-follows-view.ts @@ -1,7 +1,7 @@ import {makeAutoObservable} from 'mobx' import { AppBskyGraphGetFollows as GetFollows, - AppBskyActorRef as ActorRef, + AppBskyActorDefs as ActorDefs, } from '@atproto/api' import {RootStoreModel} from './root-store' import {cleanError} from 'lib/strings/errors' @@ -9,7 +9,7 @@ import {bundleAsync} from 'lib/async/bundle' const PAGE_SIZE = 30 -export type FollowItem = ActorRef.WithInfo +export type FollowItem = ActorDefs.ProfileViewBasic export class UserFollowsViewModel { // state @@ -22,10 +22,9 @@ export class UserFollowsViewModel { loadMoreCursor?: string // data - subject: ActorRef.WithInfo = { + subject: ActorDefs.ProfileViewBasic = { did: '', handle: '', - declaration: {cid: '', actorType: ''}, } follows: FollowItem[] = [] @@ -71,9 +70,9 @@ export class UserFollowsViewModel { try { const params = Object.assign({}, this.params, { limit: PAGE_SIZE, - before: replace ? undefined : this.loadMoreCursor, + cursor: replace ? undefined : this.loadMoreCursor, }) - const res = await this.rootStore.api.app.bsky.graph.getFollows(params) + const res = await this.rootStore.agent.getFollows(params) if (replace) { this._replaceAll(res) } else { @@ -88,13 +87,13 @@ export class UserFollowsViewModel { // state transitions // = - private _xLoading(isRefreshing = false) { + _xLoading(isRefreshing = false) { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' } - private _xIdle(err?: any) { + _xIdle(err?: any) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true @@ -107,12 +106,12 @@ export class UserFollowsViewModel { // helper functions // = - private _replaceAll(res: GetFollows.Response) { + _replaceAll(res: GetFollows.Response) { this.follows = [] this._appendAll(res) } - private _appendAll(res: GetFollows.Response) { + _appendAll(res: GetFollows.Response) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor this.follows = this.follows.concat(res.data.follows) |