Bluesky app fork with some witchin' additions 💫
witchsky.app
bluesky
fork
client
1import {useEffect, useId, useState} from 'react'
2import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
3
4import {Logger} from '#/logger'
5import {type FeedSourceInfo} from '#/state/queries/feed'
6
7/**
8 * Separate logger for better debugging
9 */
10const logger = Logger.create(Logger.Context.PostSource)
11
12export type PostSource = {
13 post: AppBskyFeedDefs.FeedViewPost
14 feedSourceInfo?: FeedSourceInfo
15}
16
17/**
18 * A cache of sources that will be consumed by the post thread view. This is
19 * cleaned up any time a source is consumed.
20 */
21const transientSources = new Map<string, PostSource>()
22
23/**
24 * A cache of sources that have been consumed by the post thread view. This is
25 * not cleaned up, but because we use a new ID for each post thread view that
26 * consumes a source, this is never reused unless a user navigates back to a
27 * post thread view that has not been dropped from memory.
28 */
29const consumedSources = new Map<string, PostSource>()
30
31/**
32 * For stashing the feed that the user was browsing when they clicked on a post.
33 *
34 * Used for FeedFeedback and other ephemeral non-critical systems.
35 */
36export function setUnstablePostSource(key: string, source: PostSource) {
37 assertValidDevOnly(
38 key,
39 `setUnstablePostSource key should be a URI containing a handle, received ${key} — use buildPostSourceKey`,
40 )
41 logger.debug('set', {key, source})
42 transientSources.set(key, source)
43}
44
45/**
46 * This hook is unstable and should only be used for FeedFeedback and other
47 * ephemeral non-critical systems. Views that use this hook will continue to
48 * return a reference to the same source until those views are dropped from
49 * memory.
50 */
51export function useUnstablePostSource(key: string) {
52 const id = useId()
53 const [source] = useState(() => {
54 assertValidDevOnly(
55 key,
56 `consumeUnstablePostSource key should be a URI containing a handle, received ${key} — be sure to use buildPostSourceKey when setting the source`,
57 true,
58 )
59 const existingSource = consumedSources.get(id) || transientSources.get(key)
60 if (existingSource) {
61 logger.debug('consume', {id, key, source: existingSource})
62 transientSources.delete(key)
63 consumedSources.set(id, existingSource)
64 }
65 return existingSource
66 })
67
68 useEffect(() => {
69 return () => {
70 consumedSources.delete(id)
71 logger.debug('cleanup', {id})
72 }
73 }, [id])
74
75 return source
76}
77
78/**
79 * Builds a post source key. This (atm) is a URI where the `host` is the post
80 * author's handle, not DID.
81 */
82export function buildPostSourceKey(key: string, handle: string) {
83 const urip = new AtUri(key)
84 // @ts-expect-error TODO new-sdk-migration
85 urip.host = handle
86 return urip.toString()
87}
88
89/**
90 * Just a lil dev helper
91 */
92function assertValidDevOnly(key: string, message: string, beChill = false) {
93 if (__DEV__) {
94 const urip = new AtUri(key)
95 if (urip.host.startsWith('did:')) {
96 if (beChill) {
97 logger.warn(message)
98 } else {
99 throw new Error(message)
100 }
101 }
102 }
103}