Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a68b716c3 | |||
| d82cd9bdb3 | |||
| abbd7461a1 | |||
| 004522466e |
@@ -214,15 +214,21 @@ func serve(cctx *cli.Context) error {
|
||||
e.GET("/iframe/youtube.html", echo.WrapHandler(staticHandler))
|
||||
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler)), func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
path := c.Request().URL.Path
|
||||
maxAge := 1 * (60 * 60) // default is 1 hour
|
||||
c.Response().Before(func() {
|
||||
if c.Response().Status >= 300 {
|
||||
return
|
||||
}
|
||||
|
||||
// all assets in /static/js, /static/css, /static/media are content-hashed and can be cached for a long time
|
||||
if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/css/") || strings.HasPrefix(path, "/static/media/") {
|
||||
maxAge = 365 * (60 * 60 * 24) // 1 year
|
||||
}
|
||||
path := c.Request().URL.Path
|
||||
maxAge := 1 * (60 * 60) // default is 1 hour
|
||||
|
||||
c.Response().Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
|
||||
// all assets in /static/js, /static/css, /static/media are content-hashed and can be cached for a long time
|
||||
if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/css/") || strings.HasPrefix(path, "/static/media/") {
|
||||
maxAge = 365 * (60 * 60 * 24) // 1 year
|
||||
}
|
||||
|
||||
c.Response().Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
|
||||
})
|
||||
return next(c)
|
||||
}
|
||||
})
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.13.21",
|
||||
"@atproto/api": "^0.13.27",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
|
||||
@@ -196,6 +196,7 @@ export function SuggestedFollowsProfile({did}: {did: string}) {
|
||||
<ProfileGrid
|
||||
isSuggestionsLoading={isSuggestionsLoading}
|
||||
profiles={data?.suggestions ?? []}
|
||||
recId={data?.recId}
|
||||
error={error}
|
||||
viewContext="profile"
|
||||
/>
|
||||
@@ -222,10 +223,12 @@ export function ProfileGrid({
|
||||
isSuggestionsLoading,
|
||||
error,
|
||||
profiles,
|
||||
recId,
|
||||
viewContext = 'feed',
|
||||
}: {
|
||||
isSuggestionsLoading: boolean
|
||||
profiles: AppBskyActorDefs.ProfileViewDetailed[]
|
||||
recId?: number
|
||||
error: Error | null
|
||||
viewContext: 'profile' | 'feed'
|
||||
}) {
|
||||
@@ -249,12 +252,20 @@ export function ProfileGrid({
|
||||
))
|
||||
) : error || !profiles.length ? null : (
|
||||
<>
|
||||
{profiles.slice(0, maxLength).map(profile => (
|
||||
{profiles.slice(0, maxLength).map((profile, index) => (
|
||||
<ProfileCard.Link
|
||||
key={profile.did}
|
||||
profile={profile}
|
||||
onPress={() => {
|
||||
logEvent('feed:interstitial:profileCard:press', {})
|
||||
logEvent('suggestedUser:press', {
|
||||
logContext:
|
||||
viewContext === 'feed'
|
||||
? 'InterstitialDiscover'
|
||||
: 'InterstitialProfile',
|
||||
recId,
|
||||
position: index,
|
||||
})
|
||||
}}
|
||||
style={[
|
||||
a.flex_1,
|
||||
@@ -282,6 +293,17 @@ export function ProfileGrid({
|
||||
logContext="FeedInterstitial"
|
||||
shape="round"
|
||||
colorInverted
|
||||
onFollow={() => {
|
||||
logEvent('suggestedUser:follow', {
|
||||
logContext:
|
||||
viewContext === 'feed'
|
||||
? 'InterstitialDiscover'
|
||||
: 'InterstitialProfile',
|
||||
location: 'Card',
|
||||
recId,
|
||||
position: index,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
<ProfileCard.Description profile={profile} numberOfLines={2} />
|
||||
|
||||
@@ -286,6 +286,7 @@ export type FollowButtonProps = {
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:unfollow']['logContext']
|
||||
colorInverted?: boolean
|
||||
onFollow?: () => void
|
||||
} & Partial<ButtonProps>
|
||||
|
||||
export function FollowButton(props: FollowButtonProps) {
|
||||
@@ -299,6 +300,7 @@ export function FollowButtonInner({
|
||||
moderationOpts,
|
||||
logContext,
|
||||
onPress: onPressProp,
|
||||
onFollow,
|
||||
colorInverted,
|
||||
...rest
|
||||
}: FollowButtonProps) {
|
||||
@@ -325,6 +327,7 @@ export function FollowButtonInner({
|
||||
),
|
||||
)
|
||||
onPressProp?.(e)
|
||||
onFollow?.()
|
||||
} catch (err: any) {
|
||||
if (err?.name !== 'AbortError') {
|
||||
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||
|
||||
@@ -63,6 +63,9 @@ export function IS_PROD_SERVICE(url?: string) {
|
||||
export const PROD_DEFAULT_FEED = (rkey: string) =>
|
||||
`at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/${rkey}`
|
||||
|
||||
export const STAGING_DEFAULT_FEED = (rkey: string) =>
|
||||
`at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/${rkey}`
|
||||
|
||||
export const POST_IMG_MAX = {
|
||||
width: 2000,
|
||||
height: 2000,
|
||||
|
||||
@@ -164,6 +164,26 @@ export type LogEvents = {
|
||||
| 'ProfileHeaderSuggestedFollows'
|
||||
| 'PostOnboardingFindFollows'
|
||||
}
|
||||
'suggestedUser:follow': {
|
||||
logContext:
|
||||
| 'Explore'
|
||||
| 'InterstitialDiscover'
|
||||
| 'InterstitialProfile'
|
||||
| 'Profile'
|
||||
location: 'Card' | 'Profile'
|
||||
recId?: number
|
||||
position: number
|
||||
}
|
||||
'suggestedUser:press': {
|
||||
logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile'
|
||||
recId?: number
|
||||
position: number
|
||||
}
|
||||
'suggestedUser:seen': {
|
||||
logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile'
|
||||
recId?: number
|
||||
position: number
|
||||
}
|
||||
'profile:unfollow': {
|
||||
logContext:
|
||||
| 'RecommendedFollowsItem'
|
||||
|
||||
@@ -4,4 +4,6 @@ export type Gate =
|
||||
| 'debug_subscriptions'
|
||||
| 'new_postonboarding'
|
||||
| 'remove_show_latest_button'
|
||||
| 'test_gate_1'
|
||||
| 'test_gate_2'
|
||||
| 'trending_topics_beta'
|
||||
|
||||
@@ -3,7 +3,7 @@ import {AppState, AppStateStatus} from 'react-native'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||
import {PROD_DEFAULT_FEED, STAGING_DEFAULT_FEED} from '#/lib/constants'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {FeedDescriptor, FeedPostSliceItem} from '#/state/queries/post-feed'
|
||||
@@ -54,7 +54,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
encoding: 'application/json',
|
||||
headers: {
|
||||
// TODO when we start sending to other feeds, we need to grab their DID -prf
|
||||
'atproto-proxy': 'did:web:discover.bsky.app#bsky_fg',
|
||||
'atproto-proxy': 'did:web:algo.pop2.bsky.app#bsky_fg',
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -155,7 +155,10 @@ export function useFeedFeedbackContext() {
|
||||
// place, we're hardcoding it to the discover feed.
|
||||
// -prf
|
||||
function isDiscoverFeed(feed: FeedDescriptor) {
|
||||
return feed === `feedgen|${PROD_DEFAULT_FEED('whats-hot')}`
|
||||
return (
|
||||
feed === `feedgen|${PROD_DEFAULT_FEED('whats-hot')}` ||
|
||||
feed === `feedgen|${STAGING_DEFAULT_FEED('thevids')}`
|
||||
)
|
||||
}
|
||||
|
||||
function toString(interaction: AppBskyFeedDefs.Interaction): string {
|
||||
|
||||
@@ -120,7 +120,7 @@ export function useSuggestedFollowsByActorQuery({
|
||||
const suggestions = res.data.isFallback
|
||||
? []
|
||||
: res.data.suggestions.filter(profile => !profile.viewer?.following)
|
||||
return {suggestions}
|
||||
return {suggestions, recId: res.data.recId}
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
|
||||
@@ -14,12 +14,14 @@ export function FollowButton({
|
||||
profile,
|
||||
labelStyle,
|
||||
logContext,
|
||||
onFollow,
|
||||
}: {
|
||||
unfollowedType?: ButtonType
|
||||
followedType?: ButtonType
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||
labelStyle?: StyleProp<TextStyle>
|
||||
logContext: 'ProfileCard' | 'StarterPackProfilesList'
|
||||
onFollow?: () => void
|
||||
}) {
|
||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||
profile,
|
||||
@@ -30,6 +32,7 @@ export function FollowButton({
|
||||
const onPressFollow = async () => {
|
||||
try {
|
||||
await queueFollow()
|
||||
onFollow?.()
|
||||
} catch (e: any) {
|
||||
if (e?.name !== 'AbortError') {
|
||||
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||
|
||||
@@ -184,6 +184,7 @@ export function ProfileCardWithFollowBtn({
|
||||
noBg,
|
||||
noBorder,
|
||||
onPress,
|
||||
onFollow,
|
||||
logContext = 'ProfileCard',
|
||||
showKnownFollowers,
|
||||
}: {
|
||||
@@ -191,6 +192,7 @@ export function ProfileCardWithFollowBtn({
|
||||
noBg?: boolean
|
||||
noBorder?: boolean
|
||||
onPress?: () => void
|
||||
onFollow?: () => void
|
||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||
showKnownFollowers?: boolean
|
||||
}) {
|
||||
@@ -206,7 +208,11 @@ export function ProfileCardWithFollowBtn({
|
||||
isMe
|
||||
? undefined
|
||||
: profileShadow => (
|
||||
<FollowButton profile={profileShadow} logContext={logContext} />
|
||||
<FollowButton
|
||||
profile={profileShadow}
|
||||
logContext={logContext}
|
||||
onFollow={onFollow}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onPress={onPress}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useMemo} from 'react'
|
||||
import React, {useCallback, useEffect, useMemo} from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
import {
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
||||
import {combinedDisplayName} from '#/lib/strings/display-names'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
@@ -496,6 +497,7 @@ function ProfileScreenLoaded({
|
||||
accessibilityHint=""
|
||||
/>
|
||||
)}
|
||||
<TestGates />
|
||||
</ScreenHider>
|
||||
)
|
||||
}
|
||||
@@ -554,3 +556,77 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
const shouldExposeToGate2 = Math.random() < 0.2
|
||||
|
||||
// --- Temporary: we're testing our Statsig setup ---
|
||||
let TestGates = React.memo(function TestGates() {
|
||||
const gate = useGate()
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('test:all:always', {})
|
||||
if (Math.random() < 0.2) {
|
||||
logEvent('test:all:sometimes', {})
|
||||
}
|
||||
if (Math.random() < 0.1) {
|
||||
logEvent('test:all:boosted_by_gate1', {
|
||||
reason: 'base',
|
||||
})
|
||||
}
|
||||
if (Math.random() < 0.1) {
|
||||
logEvent('test:all:boosted_by_gate2', {
|
||||
reason: 'base',
|
||||
})
|
||||
}
|
||||
if (Math.random() < 0.1) {
|
||||
logEvent('test:all:boosted_by_both', {
|
||||
reason: 'base',
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
return [
|
||||
gate('test_gate_1') ? <TestGate1 /> : null,
|
||||
shouldExposeToGate2 && gate('test_gate_2') ? <TestGate2 /> : null,
|
||||
]
|
||||
})
|
||||
|
||||
function TestGate1() {
|
||||
useEffect(() => {
|
||||
logEvent('test:gate1:always', {})
|
||||
if (Math.random() < 0.2) {
|
||||
logEvent('test:gate1:sometimes', {})
|
||||
}
|
||||
if (Math.random() < 0.5) {
|
||||
logEvent('test:all:boosted_by_gate1', {
|
||||
reason: 'gate1',
|
||||
})
|
||||
}
|
||||
if (Math.random() < 0.5) {
|
||||
logEvent('test:all:boosted_by_both', {
|
||||
reason: 'gate1',
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
|
||||
function TestGate2() {
|
||||
useEffect(() => {
|
||||
logEvent('test:gate2:always', {})
|
||||
if (Math.random() < 0.2) {
|
||||
logEvent('test:gate2:sometimes', {})
|
||||
}
|
||||
if (Math.random() < 0.5) {
|
||||
logEvent('test:all:boosted_by_gate2', {
|
||||
reason: 'gate2',
|
||||
})
|
||||
}
|
||||
if (Math.random() < 0.5) {
|
||||
logEvent('test:all:boosted_by_both', {
|
||||
reason: 'gate2',
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
@@ -253,6 +254,7 @@ type ExploreScreenItems =
|
||||
type: 'profile'
|
||||
key: string
|
||||
profile: AppBskyActorDefs.ProfileView
|
||||
recId?: number
|
||||
}
|
||||
| {
|
||||
type: 'feed'
|
||||
@@ -370,6 +372,7 @@ export function Explore() {
|
||||
type: 'profile',
|
||||
key: actor.did,
|
||||
profile: actor,
|
||||
recId: page.recId,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -498,7 +501,7 @@ export function Explore() {
|
||||
])
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({item}: {item: ExploreScreenItems}) => {
|
||||
({item, index}: {item: ExploreScreenItems; index: number}) => {
|
||||
switch (item.type) {
|
||||
case 'header': {
|
||||
return (
|
||||
@@ -524,6 +527,21 @@ export function Explore() {
|
||||
noBg
|
||||
noBorder
|
||||
showKnownFollowers
|
||||
onPress={() => {
|
||||
logEvent('suggestedUser:press', {
|
||||
logContext: 'Explore',
|
||||
recId: item.recId,
|
||||
position: index,
|
||||
})
|
||||
}}
|
||||
onFollow={() => {
|
||||
logEvent('suggestedUser:follow', {
|
||||
logContext: 'Explore',
|
||||
location: 'Card',
|
||||
recId: item.recId,
|
||||
position: index,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -28,6 +28,9 @@ module.exports = async function (env, argv) {
|
||||
]
|
||||
if (env.mode === 'development') {
|
||||
config.plugins.push(new ReactRefreshWebpackPlugin())
|
||||
} else {
|
||||
// Support static CDN for chunks
|
||||
config.output.publicPath = 'auto'
|
||||
}
|
||||
|
||||
if (GENERATE_STATS || OPEN_ANALYZER) {
|
||||
|
||||
@@ -72,15 +72,15 @@
|
||||
tlds "^1.234.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/api@^0.13.21":
|
||||
version "0.13.21"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.21.tgz#8ee27a07e5a024b5bf32408d9bd623dd598ad1cc"
|
||||
integrity sha512-iOxSj2YS3Fx9IPz1NivKrSsdYPNbBgpnUH7+WhKYAMvDFDUe2PZe7taau8wsUjJAu/H3S0Mk2TDh5e/7tCRwHA==
|
||||
"@atproto/api@^0.13.27":
|
||||
version "0.13.27"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.27.tgz#54d1172fbc4415d41089d2715a657052b26b60bc"
|
||||
integrity sha512-IaZezARJSoFBxFvutGa+2hT0NiA+db6NpKkla2bRyv/SPinYViuqD5kaaPzc732yeE1uwUKQc4wwwlctYamFBQ==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.3.1"
|
||||
"@atproto/lexicon" "^0.4.4"
|
||||
"@atproto/common-web" "^0.3.2"
|
||||
"@atproto/lexicon" "^0.4.5"
|
||||
"@atproto/syntax" "^0.3.1"
|
||||
"@atproto/xrpc" "^0.6.5"
|
||||
"@atproto/xrpc" "^0.6.6"
|
||||
await-lock "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
tlds "^1.234.0"
|
||||
@@ -169,6 +169,16 @@
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common-web@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.2.tgz#4cf78ad4d24fed801882f3d35afc39bceccdff51"
|
||||
integrity sha512-Vx0JtL1/CssJbFAb0UOdvTrkbUautsDfHNOXNTcX2vyPIxH9xOameSqLLunM1hZnOQbJwyjmQCt6TV+bhnanDg==
|
||||
dependencies:
|
||||
graphemer "^1.4.0"
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210"
|
||||
@@ -283,6 +293,17 @@
|
||||
multiformats "^9.9.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/lexicon@^0.4.5":
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.5.tgz#4fcf3731193c674286e9e8d677bbab5dd530b817"
|
||||
integrity sha512-fljWqMGKn+XWtTprBcS3F1hGBREnQYh6qYHv2sjENucc7REms1gtmZXSerB9N6pVeHVNOnXiILdukeAcic5OEw==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.3.2"
|
||||
"@atproto/syntax" "^0.3.1"
|
||||
iso-datestring-validator "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/oauth-provider@^0.2.10":
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901"
|
||||
@@ -452,6 +473,14 @@
|
||||
"@atproto/lexicon" "^0.4.4"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/xrpc@^0.6.6":
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.6.tgz#28f58270ef4a8056f7f718bd52512e74bcd3702f"
|
||||
integrity sha512-umXEYVMo9/pyIBoKmIAIi64RXDW9tSXY+wqztlQ6I2GZtjLfNZqmAWU+wADk3SxUe54mvjxxGyA4TtyGtDMfhA==
|
||||
dependencies:
|
||||
"@atproto/lexicon" "^0.4.5"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@aws-crypto/crc32@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa"
|
||||
|
||||
Reference in New Issue
Block a user