Resolve chat invites

This commit is contained in:
Eric Bailey
2026-06-08 15:38:18 -05:00
parent e2378f8614
commit 5e0ebcefd2
3 changed files with 38 additions and 2 deletions
@@ -69,7 +69,7 @@ function makeStore() {
return createThreadStore({ return createThreadStore({
agent, agent,
__createId: makeIdGenerator(), __createId: makeIdGenerator(),
__resolveLink: mockResolveLink as unknown as typeof resolveLink, __resolveLink: mockResolveLink,
}) })
} }
@@ -118,6 +118,13 @@ const externalLink: ResolvedLink = {
thumb: undefined, thumb: undefined,
} }
const chatInviteLink: ResolvedLink = {
type: 'chat-invite',
uri: 'https://bsky.app/messages/join/abc123',
code: 'abc123',
view: undefined,
}
describe('addUri pre-classifies bsky post URLs to the quote slot', () => { describe('addUri pre-classifies bsky post URLs to the quote slot', () => {
test('post URL → quote.pending → quote.resolved (with view)', async () => { test('post URL → quote.pending → quote.resolved (with view)', async () => {
const d = deferred<ResolvedLink>() const d = deferred<ResolvedLink>()
@@ -258,6 +265,19 @@ describe('addUri pre-classifies non-post URLs to the embed slot', () => {
expect(embed.title).toBe('Example') expect(embed.title).toBe('Example')
}) })
test('chat-invite → embed["chat-invite"]', async () => {
const d = deferred<ResolvedLink>()
mockResolveLink.mockReturnValueOnce(d.promise)
const store = makeStore()
const root = rootId(store)
store.actions.addUri(root, EXTERNAL_URL)
d.resolve(chatInviteLink)
await flushPromises()
const embed = store.getState().posts[root].embed
if (embed?.state !== 'chat-invite') throw new Error('expected chat-invite')
expect(embed.code).toBe('abc123')
})
test('addUri is a no-op when embed has settled (resolved)', async () => { test('addUri is a no-op when embed has settled (resolved)', async () => {
const d = deferred<ResolvedLink>() const d = deferred<ResolvedLink>()
mockResolveLink.mockReturnValueOnce(d.promise) mockResolveLink.mockReturnValueOnce(d.promise)
+8
View File
@@ -608,6 +608,14 @@ export function createThreadStore(options: {
thumb: link.thumb, thumb: link.thumb,
} }
} }
if (link.type === 'chat-invite') {
return {
state: 'chat-invite',
uri: link.uri,
code: link.code,
view: link.view,
}
}
switch (link.kind) { switch (link.kind) {
case 'feed': case 'feed':
return {state: 'feed', record: link.record, view: link.view} return {state: 'feed', record: link.record, view: link.view}
+9 -1
View File
@@ -2,6 +2,7 @@ import {
type AppBskyFeedDefs, type AppBskyFeedDefs,
type AppBskyGraphDefs, type AppBskyGraphDefs,
type BlobRef, type BlobRef,
type ChatBskyGroupDefs,
type ComAtprotoRepoStrongRef, type ComAtprotoRepoStrongRef,
} from '@atproto/api' } from '@atproto/api'
@@ -97,7 +98,8 @@ export type LinkResolutionFailureCode = 'embedding-disabled' | 'unknown'
* `embedding-disabled`) `retry` is omitted so UI can detect that case and * `embedding-disabled`) `retry` is omitted so UI can detect that case and
* surface a non-retryable message. The pending variant is set synchronously * surface a non-retryable message. The pending variant is set synchronously
* by addUri while resolution is in flight; the resolved variants * by addUri while resolution is in flight; the resolved variants
* (external/feed/list/starter-pack) land when the worker reports back. * (external/feed/list/starter-pack/chat-invite) land when the worker
* reports back.
* *
* Note: `retry` is a function reference and won't survive JSON serialization. * Note: `retry` is a function reference and won't survive JSON serialization.
* On restore (OS-resume / draft load), the store re-attaches it. * On restore (OS-resume / draft load), the store re-attaches it.
@@ -135,6 +137,12 @@ export type PostEmbed =
record: ComAtprotoRepoStrongRef.Main record: ComAtprotoRepoStrongRef.Main
view: AppBskyGraphDefs.StarterPackView view: AppBskyGraphDefs.StarterPackView
} }
| {
state: 'chat-invite'
uri: string
code: string
view: ChatBskyGroupDefs.JoinLinkPreviewView | undefined
}
/** /**
* What's stored on a post's `quote` field. Mirrors `PostEmbed`'s shape: the * What's stored on a post's `quote` field. Mirrors `PostEmbed`'s shape: the