93713b33df
* Use context for pending sats * Fix sats going negative on zap undo We already handle undoing pending sats by wrapping the payment+mutation with try/finally. * Remove unnecessary ItemContextProvider * Rename to parentCtx * Fix hierarchy of ItemContextProvider If a comment was root and it was zapped, the pending sats contributed to the sats shown in <CommentsHeader>. This was caused by <CommentsHeader> accessing the root item context for all comments, even for the root comment. So even if the root comment was zapped, the pending sats contributed to the sats for the comment section. This wasn't the case for posts since their item context was above the context used by <CommentsHeader>. This was fixed by moving <ItemProviderContext> down into <Comments> and <Item> instead of declaring it at <ItemFull> which wraps the root item and all comments. * Optimistic update for poll votes * prevent twice optimistic zap * enhance client notifications with skeleton and no redudant queries * enlarge nwc amount limits * Disable max amount and daily limit in NWC container --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
258 lines
7.9 KiB
JavaScript
258 lines
7.9 KiB
JavaScript
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'
|
|
import { decodeCursor, LIMIT } from './cursor'
|
|
import { SSR } from './constants'
|
|
|
|
function isFirstPage (cursor, existingThings, limit = LIMIT) {
|
|
if (cursor) {
|
|
const decursor = decodeCursor(cursor)
|
|
return decursor.offset === limit
|
|
} else {
|
|
// we don't have anything cached, or our existing items are less than
|
|
// or equal to a full page
|
|
return existingThings?.length < limit
|
|
}
|
|
}
|
|
|
|
const defaultFetchPolicy = SSR ? 'cache-only' : 'cache-first'
|
|
const defaultNextFetchPolicy = SSR ? 'cache-only' : 'cache-first'
|
|
|
|
export default function getApolloClient () {
|
|
if (SSR) {
|
|
return getClient(`${process.env.SELF_URL}/api/graphql`)
|
|
} else {
|
|
window.apolloClient ||= getClient('/api/graphql')
|
|
return window.apolloClient
|
|
}
|
|
}
|
|
|
|
function getClient (uri) {
|
|
return new ApolloClient({
|
|
link: new HttpLink({ uri }),
|
|
ssrMode: SSR,
|
|
connectToDevTools: process.env.NODE_ENV !== 'production',
|
|
cache: new InMemoryCache({
|
|
freezeResults: true,
|
|
typePolicies: {
|
|
Sub: {
|
|
keyFields: ['name'],
|
|
fields: {
|
|
optional: {
|
|
merge: true
|
|
}
|
|
}
|
|
},
|
|
User: {
|
|
// https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-non-normalized-objects
|
|
fields: {
|
|
privates: {
|
|
merge: true
|
|
},
|
|
optional: {
|
|
merge: true
|
|
}
|
|
}
|
|
},
|
|
Fact: {
|
|
keyFields: ['id', 'type']
|
|
},
|
|
Query: {
|
|
fields: {
|
|
sub: {
|
|
keyArgs: ['name'],
|
|
merge (existing, incoming) {
|
|
return incoming
|
|
}
|
|
},
|
|
topSubs: {
|
|
keyArgs: ['when', 'by', 'from', 'to', 'limit'],
|
|
merge (existing, incoming, { args }) {
|
|
if (isFirstPage(incoming.cursor, existing?.subs, args.limit)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
subs: [...(existing?.subs || []), ...incoming.subs]
|
|
}
|
|
}
|
|
},
|
|
topUsers: {
|
|
keyArgs: ['when', 'by', 'from', 'to', 'limit'],
|
|
merge (existing, incoming, { args }) {
|
|
if (isFirstPage(incoming.cursor, existing?.users, args.limit)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
users: [...(existing?.users || []), ...incoming.users]
|
|
}
|
|
}
|
|
},
|
|
mySubscribedUsers: {
|
|
keyArgs: false,
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.users)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
users: [...(existing?.users || []), ...incoming.users]
|
|
}
|
|
}
|
|
},
|
|
myMutedUsers: {
|
|
keyArgs: false,
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.users)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
users: [...(existing?.users || []), ...incoming.users]
|
|
}
|
|
}
|
|
},
|
|
userSuggestions: {
|
|
keyArgs: ['q', 'limit'],
|
|
merge (existing, incoming) {
|
|
return incoming
|
|
}
|
|
},
|
|
topCowboys: {
|
|
keyArgs: [],
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.users)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
users: [...(existing?.users || []), ...incoming.users]
|
|
}
|
|
}
|
|
},
|
|
items: {
|
|
keyArgs: ['sub', 'sort', 'type', 'name', 'when', 'by', 'from', 'to', 'limit'],
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.items)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
items: [...(existing?.items || []), ...incoming.items],
|
|
pins: [...(existing?.pins || []), ...(incoming.pins || [])]
|
|
}
|
|
}
|
|
},
|
|
comments: {
|
|
keyArgs: ['id', 'sort'],
|
|
merge (existing, incoming) {
|
|
return incoming
|
|
}
|
|
},
|
|
related: {
|
|
keyArgs: ['id', 'title', 'minMatch', 'limit'],
|
|
merge (existing, incoming, { args }) {
|
|
if (isFirstPage(incoming.cursor, existing?.items, args.limit)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
items: [...(existing?.items || []), ...incoming.items]
|
|
}
|
|
}
|
|
},
|
|
search: {
|
|
keyArgs: ['q', 'sub', 'sort', 'what', 'when', 'from', 'to', 'limit'],
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.items)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
items: [...(existing?.items || []), ...incoming.items]
|
|
}
|
|
}
|
|
},
|
|
searchUsers: {
|
|
keyArgs: ['q', 'limit', 'similarity'],
|
|
merge (existing, incoming) {
|
|
return [...(existing?.searchUsers || []), ...incoming]
|
|
}
|
|
},
|
|
notifications: {
|
|
keyArgs: ['inc'],
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.notifications)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
earn: existing?.earn || incoming.earn,
|
|
notifications: [...(existing?.notifications || []), ...incoming.notifications],
|
|
lastChecked: existing?.lastChecked || incoming.lastChecked
|
|
}
|
|
}
|
|
},
|
|
numBolt11s: {
|
|
keyArgs: [],
|
|
merge (existing, incoming) {
|
|
return incoming
|
|
}
|
|
},
|
|
walletHistory: {
|
|
keyArgs: ['inc'],
|
|
merge (existing, incoming) {
|
|
if (isFirstPage(incoming.cursor, existing?.facts)) {
|
|
return incoming
|
|
}
|
|
|
|
return {
|
|
cursor: incoming.cursor,
|
|
facts: [...(existing?.facts || []), ...incoming.facts]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Item: {
|
|
fields: {
|
|
meAnonSats: {
|
|
read (meAnonSats, { readField }) {
|
|
if (typeof window === 'undefined') return null
|
|
const itemId = readField('id')
|
|
return meAnonSats ?? Number(window.localStorage.getItem(`TIP-item:${itemId}`) || '0')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
assumeImmutableResults: true,
|
|
queryDeduplication: true,
|
|
defaultOptions: {
|
|
watchQuery: {
|
|
initialFetchPolicy: defaultFetchPolicy,
|
|
fetchPolicy: defaultFetchPolicy,
|
|
nextFetchPolicy: defaultNextFetchPolicy,
|
|
canonizeResults: true,
|
|
ssr: SSR
|
|
},
|
|
query: {
|
|
initialFetchPolicy: defaultFetchPolicy,
|
|
fetchPolicy: defaultFetchPolicy,
|
|
nextFetchPolicy: defaultNextFetchPolicy,
|
|
canonizeResults: true,
|
|
ssr: SSR
|
|
}
|
|
}
|
|
})
|
|
}
|