Lex refactor (#362)
* Remove the hackcheck for upgrades * Rename the PostEmbeds folder to match the codebase style * Updates to latest lex refactor * Update to use new bsky agent * Update to use api package's richtext library * Switch to upsertProfile * Add TextEncoder/TextDecoder polyfill * Add Intl.Segmenter polyfill * Update composer to calculate lengths by grapheme * Fix detox * Fix login in e2e * Create account e2e passing * Implement an e2e mocking framework * Don't use private methods on mobx models as mobx can't track them * Add tooling for e2e-specific builds and add e2e media-picker mock * Add some tests and fix some bugs around profile editing * Add shell tests * Add home screen tests * Add thread screen tests * Add tests for other user profile screens * Add search screen tests * Implement profile imagery change tools and tests * Update to new embed behaviors * Add post tests * Fix to profile-screen test * Fix session resumption * Update web composer to new api * 1.11.0 * Fix pagination cursor parameters * Add quote posts to notifications * Fix embed layouts * Remove youtube inline player and improve tap handling on link cards * Reset minimal shell mode on all screen loads and feed swipes (close #299) * Update podfile.lock * Improve post notfound UI (close #366) * Bump atproto packages
This commit is contained in:
parent
19f3a2fa92
commit
a3334a01a2
133 changed files with 3103 additions and 2839 deletions
|
@ -1,11 +1,11 @@
|
|||
import AtpAgent from '@atproto/api'
|
||||
import {BskyAgent, stringifyLex, jsonToLex} from '@atproto/api'
|
||||
import RNFS from 'react-native-fs'
|
||||
|
||||
const GET_TIMEOUT = 15e3 // 15s
|
||||
const POST_TIMEOUT = 60e3 // 60s
|
||||
|
||||
export function doPolyfill() {
|
||||
AtpAgent.configure({fetch: fetchHandler})
|
||||
BskyAgent.configure({fetch: fetchHandler})
|
||||
}
|
||||
|
||||
interface FetchHandlerResponse {
|
||||
|
@ -22,7 +22,7 @@ async function fetchHandler(
|
|||
): Promise<FetchHandlerResponse> {
|
||||
const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
|
||||
if (reqMimeType && reqMimeType.startsWith('application/json')) {
|
||||
reqBody = JSON.stringify(reqBody)
|
||||
reqBody = stringifyLex(reqBody)
|
||||
} else if (
|
||||
typeof reqBody === 'string' &&
|
||||
(reqBody.startsWith('/') || reqBody.startsWith('file:'))
|
||||
|
@ -65,7 +65,7 @@ async function fetchHandler(
|
|||
let resBody
|
||||
if (resMimeType) {
|
||||
if (resMimeType.startsWith('application/json')) {
|
||||
resBody = await res.json()
|
||||
resBody = jsonToLex(await res.json())
|
||||
} else if (resMimeType.startsWith('text/')) {
|
||||
resBody = await res.text()
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export function doPolyfill() {
|
||||
// TODO needed? native fetch may work fine -prf
|
||||
// AtpApi.xrpc.fetch = fetchHandler
|
||||
// no polyfill is needed on web
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {RootStoreModel} from 'state/index'
|
||||
import {
|
||||
AppBskyFeedFeedViewPost,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetAuthorFeed as GetAuthorFeed,
|
||||
} from '@atproto/api'
|
||||
type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost
|
||||
type ReasonRepost = AppBskyFeedDefs.ReasonRepost
|
||||
|
||||
async function getMultipleAuthorsPosts(
|
||||
rootStore: RootStoreModel,
|
||||
|
@ -12,12 +12,12 @@ async function getMultipleAuthorsPosts(
|
|||
limit: number = 10,
|
||||
) {
|
||||
const responses = await Promise.all(
|
||||
authors.map((author, index) =>
|
||||
rootStore.api.app.bsky.feed
|
||||
authors.map((actor, index) =>
|
||||
rootStore.agent
|
||||
.getAuthorFeed({
|
||||
author,
|
||||
actor,
|
||||
limit,
|
||||
before: cursor ? cursor.split(',')[index] : undefined,
|
||||
cursor: cursor ? cursor.split(',')[index] : undefined,
|
||||
})
|
||||
.catch(_err => ({success: false, headers: {}, data: {feed: []}})),
|
||||
),
|
||||
|
@ -29,14 +29,14 @@ function mergePosts(
|
|||
responses: GetAuthorFeed.Response[],
|
||||
{repostsOnly, bestOfOnly}: {repostsOnly?: boolean; bestOfOnly?: boolean},
|
||||
) {
|
||||
let posts: AppBskyFeedFeedViewPost.Main[] = []
|
||||
let posts: AppBskyFeedDefs.FeedViewPost[] = []
|
||||
|
||||
if (bestOfOnly) {
|
||||
for (const res of responses) {
|
||||
if (res.success) {
|
||||
// filter the feed down to the post with the most upvotes
|
||||
// filter the feed down to the post with the most likes
|
||||
res.data.feed = res.data.feed.reduce(
|
||||
(acc: AppBskyFeedFeedViewPost.Main[], v) => {
|
||||
(acc: AppBskyFeedDefs.FeedViewPost[], v) => {
|
||||
if (
|
||||
!acc?.[0] &&
|
||||
!v.reason &&
|
||||
|
@ -49,7 +49,7 @@ function mergePosts(
|
|||
acc &&
|
||||
!v.reason &&
|
||||
!v.reply &&
|
||||
v.post.upvoteCount > acc[0]?.post.upvoteCount &&
|
||||
(v.post.likeCount || 0) > (acc[0]?.post.likeCount || 0) &&
|
||||
isRecentEnough(v.post.indexedAt)
|
||||
) {
|
||||
return [v]
|
||||
|
@ -92,7 +92,7 @@ function mergePosts(
|
|||
return posts
|
||||
}
|
||||
|
||||
function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean {
|
||||
function isARepostOfSomeoneElse(post: AppBskyFeedDefs.FeedViewPost): boolean {
|
||||
return (
|
||||
post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' &&
|
||||
post.post.author.did !== (post.reason as ReasonRepost).by.did
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {AppBskyFeedFeedViewPost} from '@atproto/api'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import lande from 'lande'
|
||||
type FeedViewPost = AppBskyFeedFeedViewPost.Main
|
||||
import {hasProp} from '@atproto/lexicon'
|
||||
import {hasProp} from 'lib/type-guards'
|
||||
import {LANGUAGES_MAP_CODE2} from '../../locale/languages'
|
||||
type FeedViewPost = AppBskyFeedDefs.FeedViewPost
|
||||
|
||||
export type FeedTunerFn = (
|
||||
tuner: FeedTuner,
|
||||
|
@ -174,7 +174,7 @@ export class FeedTuner {
|
|||
}
|
||||
const item = slices[i].rootItem
|
||||
const isRepost = Boolean(item.reason)
|
||||
if (!isRepost && item.post.upvoteCount < 2) {
|
||||
if (!isRepost && (item.post.likeCount || 0) < 2) {
|
||||
slices.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import {
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedExternal,
|
||||
ComAtprotoBlobUpload,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
ComAtprotoRepoUploadBlob,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '../../third-party/uri'
|
||||
import {RootStoreModel} from 'state/models/root-store'
|
||||
import {extractEntities} from 'lib/strings/rich-text-detection'
|
||||
import {isNetworkError} from 'lib/strings/errors'
|
||||
import {LinkMeta} from '../link-meta/link-meta'
|
||||
import {Image} from '../media/manip'
|
||||
import {RichText} from '../strings/rich-text'
|
||||
import {isWeb} from 'platform/detection'
|
||||
|
||||
export interface ExternalEmbedDraft {
|
||||
|
@ -27,7 +27,7 @@ export async function resolveName(store: RootStoreModel, didOrHandle: string) {
|
|||
if (didOrHandle.startsWith('did:')) {
|
||||
return didOrHandle
|
||||
}
|
||||
const res = await store.api.com.atproto.handle.resolve({
|
||||
const res = await store.agent.resolveHandle({
|
||||
handle: didOrHandle,
|
||||
})
|
||||
return res.data.did
|
||||
|
@ -37,15 +37,15 @@ export async function uploadBlob(
|
|||
store: RootStoreModel,
|
||||
blob: string,
|
||||
encoding: string,
|
||||
): Promise<ComAtprotoBlobUpload.Response> {
|
||||
): Promise<ComAtprotoRepoUploadBlob.Response> {
|
||||
if (isWeb) {
|
||||
// `blob` should be a data uri
|
||||
return store.api.com.atproto.blob.upload(convertDataURIToUint8Array(blob), {
|
||||
return store.agent.uploadBlob(convertDataURIToUint8Array(blob), {
|
||||
encoding,
|
||||
})
|
||||
} else {
|
||||
// `blob` should be a path to a file in the local FS
|
||||
return store.api.com.atproto.blob.upload(
|
||||
return store.agent.uploadBlob(
|
||||
blob, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
|
||||
{encoding},
|
||||
)
|
||||
|
@ -70,22 +70,18 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
| AppBskyEmbedImages.Main
|
||||
| AppBskyEmbedExternal.Main
|
||||
| AppBskyEmbedRecord.Main
|
||||
| AppBskyEmbedRecordWithMedia.Main
|
||||
| undefined
|
||||
let reply
|
||||
const text = new RichText(opts.rawText, undefined, {
|
||||
cleanNewlines: true,
|
||||
}).text.trim()
|
||||
const rt = new RichText(
|
||||
{text: opts.rawText.trim()},
|
||||
{
|
||||
cleanNewlines: true,
|
||||
},
|
||||
)
|
||||
|
||||
opts.onStateChange?.('Processing...')
|
||||
const entities = extractEntities(text, opts.knownHandles)
|
||||
if (entities) {
|
||||
for (const ent of entities) {
|
||||
if (ent.type === 'mention') {
|
||||
const prof = await store.profiles.getProfile(ent.value)
|
||||
ent.value = prof.data.did
|
||||
}
|
||||
}
|
||||
}
|
||||
await rt.detectFacets(store.agent)
|
||||
|
||||
if (opts.quote) {
|
||||
embed = {
|
||||
|
@ -95,24 +91,37 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
cid: opts.quote.cid,
|
||||
},
|
||||
} as AppBskyEmbedRecord.Main
|
||||
} else if (opts.images?.length) {
|
||||
embed = {
|
||||
$type: 'app.bsky.embed.images',
|
||||
images: [],
|
||||
} as AppBskyEmbedImages.Main
|
||||
let i = 1
|
||||
}
|
||||
|
||||
if (opts.images?.length) {
|
||||
const images: AppBskyEmbedImages.Image[] = []
|
||||
for (const image of opts.images) {
|
||||
opts.onStateChange?.(`Uploading image #${i++}...`)
|
||||
opts.onStateChange?.(`Uploading image #${images.length + 1}...`)
|
||||
const res = await uploadBlob(store, image, 'image/jpeg')
|
||||
embed.images.push({
|
||||
image: {
|
||||
cid: res.data.cid,
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
images.push({
|
||||
image: res.data.blob,
|
||||
alt: '', // TODO supply alt text
|
||||
})
|
||||
}
|
||||
} else if (opts.extLink) {
|
||||
|
||||
if (opts.quote) {
|
||||
embed = {
|
||||
$type: 'app.bsky.embed.recordWithMedia',
|
||||
record: embed,
|
||||
media: {
|
||||
$type: 'app.bsky.embed.images',
|
||||
images,
|
||||
},
|
||||
} as AppBskyEmbedRecordWithMedia.Main
|
||||
} else {
|
||||
embed = {
|
||||
$type: 'app.bsky.embed.images',
|
||||
images,
|
||||
} as AppBskyEmbedImages.Main
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.extLink && !opts.images?.length) {
|
||||
let thumb
|
||||
if (opts.extLink.localThumb) {
|
||||
opts.onStateChange?.('Uploading link thumbnail...')
|
||||
|
@ -138,27 +147,41 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
opts.extLink.localThumb.path,
|
||||
encoding,
|
||||
)
|
||||
thumb = {
|
||||
cid: thumbUploadRes.data.cid,
|
||||
mimeType: encoding,
|
||||
}
|
||||
thumb = thumbUploadRes.data.blob
|
||||
}
|
||||
}
|
||||
embed = {
|
||||
$type: 'app.bsky.embed.external',
|
||||
external: {
|
||||
uri: opts.extLink.uri,
|
||||
title: opts.extLink.meta?.title || '',
|
||||
description: opts.extLink.meta?.description || '',
|
||||
thumb,
|
||||
},
|
||||
} as AppBskyEmbedExternal.Main
|
||||
|
||||
if (opts.quote) {
|
||||
embed = {
|
||||
$type: 'app.bsky.embed.recordWithMedia',
|
||||
record: embed,
|
||||
media: {
|
||||
$type: 'app.bsky.embed.external',
|
||||
external: {
|
||||
uri: opts.extLink.uri,
|
||||
title: opts.extLink.meta?.title || '',
|
||||
description: opts.extLink.meta?.description || '',
|
||||
thumb,
|
||||
},
|
||||
} as AppBskyEmbedExternal.Main,
|
||||
} as AppBskyEmbedRecordWithMedia.Main
|
||||
} else {
|
||||
embed = {
|
||||
$type: 'app.bsky.embed.external',
|
||||
external: {
|
||||
uri: opts.extLink.uri,
|
||||
title: opts.extLink.meta?.title || '',
|
||||
description: opts.extLink.meta?.description || '',
|
||||
thumb,
|
||||
},
|
||||
} as AppBskyEmbedExternal.Main
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.replyTo) {
|
||||
const replyToUrip = new AtUri(opts.replyTo)
|
||||
const parentPost = await store.api.app.bsky.feed.post.get({
|
||||
user: replyToUrip.host,
|
||||
const parentPost = await store.agent.getPost({
|
||||
repo: replyToUrip.host,
|
||||
rkey: replyToUrip.rkey,
|
||||
})
|
||||
if (parentPost) {
|
||||
|
@ -175,16 +198,12 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
|
||||
try {
|
||||
opts.onStateChange?.('Posting...')
|
||||
return await store.api.app.bsky.feed.post.create(
|
||||
{did: store.me.did || ''},
|
||||
{
|
||||
text,
|
||||
reply,
|
||||
embed,
|
||||
entities,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
return await store.agent.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
reply,
|
||||
embed,
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error(`Failed to create post: ${e.toString()}`)
|
||||
if (isNetworkError(e)) {
|
||||
|
@ -197,49 +216,6 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function repost(store: RootStoreModel, uri: string, cid: string) {
|
||||
return await store.api.app.bsky.feed.repost.create(
|
||||
{did: store.me.did || ''},
|
||||
{
|
||||
subject: {uri, cid},
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export async function unrepost(store: RootStoreModel, repostUri: string) {
|
||||
const repostUrip = new AtUri(repostUri)
|
||||
return await store.api.app.bsky.feed.repost.delete({
|
||||
did: repostUrip.hostname,
|
||||
rkey: repostUrip.rkey,
|
||||
})
|
||||
}
|
||||
|
||||
export async function follow(
|
||||
store: RootStoreModel,
|
||||
subjectDid: string,
|
||||
subjectDeclarationCid: string,
|
||||
) {
|
||||
return await store.api.app.bsky.graph.follow.create(
|
||||
{did: store.me.did || ''},
|
||||
{
|
||||
subject: {
|
||||
did: subjectDid,
|
||||
declarationCid: subjectDeclarationCid,
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export async function unfollow(store: RootStoreModel, followUri: string) {
|
||||
const followUrip = new AtUri(followUri)
|
||||
return await store.api.app.bsky.graph.follow.delete({
|
||||
did: followUrip.hostname,
|
||||
rkey: followUrip.rkey,
|
||||
})
|
||||
}
|
||||
|
||||
// helpers
|
||||
// =
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue