From 2d23d821d395dd81691f7a1590800e122b59e3c6 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 30 Jul 2025 10:26:10 -0700 Subject: [PATCH 01/22] Update golang toolchain to 1.24 and clean imports --- Dockerfile | 2 +- Dockerfile.embedr | 2 +- bskyweb/cmd/embedr/server.go | 4 +- bskyweb/go.mod | 40 ++++++++--------- bskyweb/go.sum | 84 ++++++++++++++++++------------------ 5 files changed, 67 insertions(+), 65 deletions(-) diff --git a/Dockerfile b/Dockerfile index 50dc28b4b3..353d1e53bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23-bullseye AS build-env +FROM golang:1.24-bullseye AS build-env WORKDIR /usr/src/social-app diff --git a/Dockerfile.embedr b/Dockerfile.embedr index 46719dc438..4a536f70fc 100644 --- a/Dockerfile.embedr +++ b/Dockerfile.embedr @@ -1,4 +1,4 @@ -FROM golang:1.23-bullseye AS build-env +FROM golang:1.24-bullseye AS build-env WORKDIR /usr/src/social-app diff --git a/bskyweb/cmd/embedr/server.go b/bskyweb/cmd/embedr/server.go index 07c25afad7..d8df4276f2 100644 --- a/bskyweb/cmd/embedr/server.go +++ b/bskyweb/cmd/embedr/server.go @@ -13,6 +13,8 @@ import ( "syscall" "time" + _ "net/http/pprof" + "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/util/cliutil" "github.com/bluesky-social/indigo/xrpc" @@ -70,7 +72,7 @@ func serve(cctx *cli.Context) error { return err } - metricsMux := http.NewServeMux() + metricsMux := http.DefaultServeMux metricsMux.Handle("/metrics", promhttp.Handler()) metricsHttpd := &http.Server{ diff --git a/bskyweb/go.mod b/bskyweb/go.mod index 630d5d256b..0a76e56eef 100644 --- a/bskyweb/go.mod +++ b/bskyweb/go.mod @@ -1,16 +1,18 @@ module github.com/bluesky-social/social-app/bskyweb -go 1.23 +go 1.23.0 -toolchain go1.23.4 +toolchain go1.24.5 require ( github.com/bluesky-social/indigo v0.0.0-20250605010711-db9bb60025dc github.com/flosch/pongo2/v6 v6.0.0 github.com/ipfs/go-log v1.0.5 github.com/joho/godotenv v1.5.1 - github.com/klauspost/compress v1.17.3 - github.com/labstack/echo/v4 v4.11.3 + github.com/klauspost/compress v1.18.0 + github.com/labstack/echo-contrib v0.17.4 + github.com/labstack/echo/v4 v4.13.4 + github.com/prometheus/client_golang v1.22.0 github.com/urfave/cli/v2 v2.25.7 ) @@ -18,7 +20,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/carlmjohnson/versioninfo v0.22.5 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -26,7 +28,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect @@ -52,17 +53,16 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/labstack/gommon v0.4.1 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/jwx/v2 v2.0.12 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect @@ -70,13 +70,13 @@ require ( github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-varint v0.0.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/orandin/slog-gorm v1.3.2 // indirect github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect - github.com/prometheus/client_golang v1.17.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -94,14 +94,14 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/time v0.12.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gorm.io/driver/postgres v1.5.7 // indirect gorm.io/driver/sqlite v1.5.5 // indirect gorm.io/gorm v1.25.9 // indirect diff --git a/bskyweb/go.sum b/bskyweb/go.sum index c0ba4f6cc1..76a7c4b5bf 100644 --- a/bskyweb/go.sum +++ b/bskyweb/go.sum @@ -8,8 +8,8 @@ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8b github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -33,10 +33,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -102,8 +100,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= -github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -113,10 +111,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= -github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= -github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk= -github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= +github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= +github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= +github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -130,16 +132,13 @@ github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -154,6 +153,8 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/orandin/slog-gorm v1.3.2 h1:C0lKDQPAx/pF+8K2HL7bdShPwOEJpPM0Bn80zTzxU1g= @@ -163,14 +164,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -197,8 +198,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= @@ -252,8 +253,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -271,16 +272,16 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -292,13 +293,12 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -310,10 +310,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -332,8 +332,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From d4b23d3ab4e8448321fecc7bd46b6531ada80348 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 30 Jul 2025 12:33:40 -0500 Subject: [PATCH 02/22] [APP-1173] Clean up env (#8701) * Clean up env files * Use new env in Sentry setup file * Use new env in Bitdrift setup file * Use new env in chat proxy header * Prefix Bitdrift key with EXPO_PUBLIC * Deprecate SENTRY_RELEASE since we use package.json now * Use existing EXPO_PUBLIC_BUNDLE_IDENTIFIER short commit has as Sentry dist value * Fix missing bundle identifier for Render deploys * Deprecate SENTRY_DIST in favor of EXPO_PUBLIC_BUNDLE_IDENTIFIER * Prefix SENTRY_DSN with EXPO_PUBLIC to match others * Remove debugging field * Replace NODE_ENV in places where its safe * Self review * Properly patch Sentry package * Echo variables to .env in Dockerfile instead of passing to shell script * Make sure EXPO_PUBLIC_ENV is set for web container builds * Update IS_TESTFLIGHT to include testflight-android * Slice bundle hash to match other platforms, needed for render.com deployments * [APP-1331] Migrate `app-info` to new env (#8703) * Move env files into directory with platform specific files * Migrate usages of app-info to new env * Fix bad import * Update BUNDLE_DATE format comment * Trim RENDER_GIT_COMMIT to first 7 to match --short sha * Clarify build process env vars and ensure they are explicitly passed in * Revert Sentry patch as a result of prev commit * Update webpack Sentry dist value based on prev commits * Add PACKAGE_VERSION and replace in statsig to fix conflict * Fix render substitution syntax * Remove invalid syntax * Remove unnecessary testflight check * Just use long commit hash * Slice full hash for display in app * Fix missing space in ios workflow * Pass in sentry CLI env vars, align matching values * Align on RELEASE_VERSION * Add new env setup to missed OTA spot * Update webpack to use same SENTRY_RELEASE var * Just fallback to package version for Render deploys * Remove TF check for BUNDLE_DATE * Set EXPO_PUBLIC_ENV for bundle update * Consistent naming "Env" * Add comment * Use RELEASE_VERSION instead of package.json * Update PR comment CI --- .env.example | 34 ++++++-- .../workflows/build-and-push-bskyweb-aws.yaml | 17 ++-- .github/workflows/build-submit-android.yml | 36 ++++++--- .github/workflows/build-submit-ios.yml | 25 +++--- .../workflows/bundle-deploy-eas-update.yml | 67 +++++++++++----- .github/workflows/pull-request-comment.yml | 24 +++--- Dockerfile | 29 ++++--- docs/build.md | 4 +- src/components/PostControls/DiscoverDebug.tsx | 2 +- .../PostControls/PostMenu/PostMenuItems.tsx | 2 +- src/env.ts | 6 -- src/env/common.ts | 79 +++++++++++++++++++ src/env/index.ts | 19 +++++ src/env/index.web.ts | 15 ++++ src/lib/app-info.ts | 18 ----- src/lib/app-info.web.ts | 18 ----- src/lib/hooks/useOTAUpdates.ts | 2 +- src/lib/statsig/statsig.tsx | 20 +++-- src/logger/bitdrift/setup/index.ts | 3 +- src/logger/index.ts | 3 +- src/logger/sentry/setup/index.ts | 29 ++----- src/screens/Settings/AboutSettings.tsx | 10 +-- .../Settings/AppIconSettings/index.tsx | 8 +- src/screens/Settings/AppearanceSettings.tsx | 2 +- src/screens/Settings/Settings.tsx | 2 +- src/state/queries/messages/const.ts | 4 +- src/state/session/logging.ts | 10 +-- webpack.config.js | 2 +- 28 files changed, 313 insertions(+), 177 deletions(-) delete mode 100644 src/env.ts create mode 100644 src/env/common.ts create mode 100644 src/env/index.ts create mode 100644 src/env/index.web.ts delete mode 100644 src/lib/app-info.ts delete mode 100644 src/lib/app-info.web.ts diff --git a/.env.example b/.env.example index a4d21ac332..e18cda4e55 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,32 @@ -# Copy this to `.env` and `.env.test` files +# The env the app is running in e.g. development, testflight, production +EXPO_PUBLIC_ENV=development -BITDRIFT_API_KEY= -SENTRY_AUTH_TOKEN= -EXPO_PUBLIC_LOG_LEVEL=debug -EXPO_PUBLIC_LOG_DEBUG= +# This is the semver release version of the app, pulled from package.json +EXPO_PUBLIC_RELEASE_VERSION= + +# This is the commit hash that the current bundle was made from. EXPO_PUBLIC_BUNDLE_IDENTIFIER= + +# Should be formatted YYMMDDHH so that it increases for each build. EXPO_PUBLIC_BUNDLE_DATE=0 + +# The log level for the app's logger transports +EXPO_PUBLIC_LOG_LEVEL=debug + +# Enable debug logs for specific logger instances +EXPO_PUBLIC_LOG_DEBUG=session + +# Chat service DID +EXPO_PUBLIC_CHAT_PROXY_DID= + +# +# +# Bluesky specific values +# +# + +# Sentry DSN for telemetry +EXPO_PUBLIC_SENTRY_DSN= + +# Bitdrift API key. If undefined, Bitdrift will be disabled. +EXPO_PUBLIC_BITDRIFT_API_KEY= diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 399e8669f0..6d573b0f71 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -43,12 +43,11 @@ jobs: tags: | type=sha,enable=true,priority=100,prefix=,suffix=,format=long - - name: Set outputs - id: vars + - name: Env + id: env run: | - echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "SENTRY_DIST=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - name: Build and push Docker image id: build-and-push @@ -62,8 +61,8 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max build-args: | - EXPO_PUBLIC_BUNDLE_IDENTIFIER=${{ steps.vars.outputs.sha_short }} - SENTRY_DIST=${{ steps.vars.outputs.SENTRY_DIST }} - SENTRY_RELEASE=${{ steps.vars.outputs.SENTRY_RELEASE }} + EXPO_PUBLIC_ENV=production + EXPO_PUBLIC_RELEASE_VERSION=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + EXPO_PUBLIC_BUNDLE_IDENTIFIER=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_DSN=${{ secrets.SENTRY_DSN }} diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index e862a701fd..f1e75f87e5 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -62,23 +62,30 @@ jobs: - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi - - name: ✏️ Write environment variables + # EXPO_PUBLIC_ENV is handled in eas.json + - name: Env + id: env run: | export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env + echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env + echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "$json" > google-services.json - - name: Setup Sentry vars for build-time injection - id: sentry - run: | - echo "SENTRY_DIST=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT - - name: 🏗️ EAS Build - run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} yarn use-build-number-with-bump eas build -p android --profile ${{ inputs.profile || 'testflight-android' }} --local --output build.aab --non-interactive + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn use-build-number-with-bump + eas build -p android + --profile ${{ inputs.profile || 'testflight-android' }} + --local --output build.aab --non-interactive - name: ✍️ Rename Testflight bundle if: ${{ inputs.profile != 'production' }} @@ -140,7 +147,14 @@ jobs: - name: 🏗️ Build Production APK if: ${{ inputs.profile == 'production' }} - run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} yarn use-build-number-with-bump eas build -p android --profile production-apk --local --output build.apk --non-interactive + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn use-build-number-with-bump + eas build -p android + --profile production-apk + --local --output build.apk --non-interactive - name: 🚀 Upload Production APK Artifact id: upload-artifact-production-apk diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index a197695db7..f3759d0dd4 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -75,22 +75,29 @@ jobs: - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi + # EXPO_PUBLIC_ENV is handled in eas.json - name: ✏️ Write environment variables + id: env run: | echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env + echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env + echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json - - name: Setup Sentry vars for build-time injection - id: sentry - run: | - echo "SENTRY_DIST=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT - - name: 🏗️ EAS Build - run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} yarn use-build-number-with-bump eas build -p ios --profile ${{ inputs.profile || 'testflight' }} --local --output build.ipa --non-interactive + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn use-build-number-with-bump + eas build -p ios + --profile ${{ inputs.profile || 'testflight' }} + --local --output build.ipa --non-interactive - name: 🚀 Deploy run: eas submit -p ios --non-interactive --path build.ipa diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 475bc087f1..1bd0b71c47 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -101,25 +101,30 @@ jobs: if: ${{ !steps.fingerprint.outputs.includes-changes }} uses: dcarbone/install-jq-action@v2 - - name: ✏️ Write environment variables + # eas.json not used here, set EXPO_PUBLIC_ENV + - name: Env + id: env if: ${{ !steps.fingerprint.outputs.includes-changes }} run: | export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env + echo "EXPO_PUBLIC_ENV=${{ inputs.channel || 'testflight' }}" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env + echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env + echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "$json" > google-services.json - - name: Setup Sentry vars for build-time injection - id: sentry - run: | - echo "SENTRY_DIST=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT - - name: 🏗️ Create Bundle if: ${{ !steps.fingerprint.outputs.includes-changes }} - run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn export - name: 📦 Package Bundle and 🚀 Deploy if: ${{ !steps.fingerprint.outputs.includes-changes }} @@ -205,16 +210,29 @@ jobs: - name: 🔤 Compile translations run: yarn intl:build - - name: ✏️ Write environment variables + # EXPO_PUBLIC_ENV is handled in eas.json + - name: Env + id: env run: | echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env + echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env + echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json - name: 🏗️ EAS Build - run: yarn use-build-number-with-bump eas build -p ios --profile testflight --local --output build.ipa --non-interactive + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn use-build-number-with-bump + eas build -p ios + --profile testflight + --local --output build.ipa --non-interactive - name: 🚀 Deploy run: eas submit -p ios --non-interactive --path build.ipa @@ -282,17 +300,30 @@ jobs: - name: 🔤 Compile translations run: yarn intl:build - - name: ✏️ Write environment variables + # EXPO_PUBLIC_ENV is handled in eas.json + - name: Env + id: env run: | export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env + echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env + echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "$json" > google-services.json - name: 🏗️ EAS Build - run: yarn use-build-number-with-bump eas build -p android --profile testflight-android --local --output build.apk --non-interactive + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn use-build-number-with-bump + eas build -p android + --profile testflight-android + --local --output build.apk --non-interactive - name: ⏰ Get a timestamp id: timestamp diff --git a/.github/workflows/pull-request-comment.yml b/.github/workflows/pull-request-comment.yml index e40eff6c7d..d043010027 100644 --- a/.github/workflows/pull-request-comment.yml +++ b/.github/workflows/pull-request-comment.yml @@ -152,23 +152,27 @@ jobs: - name: 🪛 Setup jq uses: dcarbone/install-jq-action@v2 - - name: ✏️ Write environment variables + - name: Env + id: env run: | export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env + echo "EXPO_PUBLIC_ENV=testflight" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env + echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env + echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env + echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env + echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "$json" > google-services.json - - name: Setup Sentry vars for build-time injection - id: sentry - run: | - echo "SENTRY_DIST=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "SENTRY_RELEASE=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT - - name: 🏗️ Create Bundle - run: SENTRY_DIST=${{ steps.sentry.outputs.SENTRY_DIST }} SENTRY_RELEASE=${{ steps.sentry.outputs.SENTRY_RELEASE }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_DSN=${{ secrets.SENTRY_DSN }} EXPO_PUBLIC_ENV="testflight" yarn export + run: > + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} + SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + yarn export - name: 📦 Package Bundle and 🚀 Deploy run: yarn use-build-number bash scripts/bundleUpdate.sh diff --git a/Dockerfile b/Dockerfile index 50dc28b4b3..43d7f4eb48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,28 +19,28 @@ ENV GOARCH="amd64" ENV CGO_ENABLED=1 ENV GOEXPERIMENT="loopvar" +# The latest git hash of the preview branch on render.com +# https://render.com/docs/docker-secrets#environment-variables-in-docker-builds +ARG RENDER_GIT_COMMIT + # # Expo # +ARG EXPO_PUBLIC_ENV +ENV EXPO_PUBLIC_ENV=${EXPO_PUBLIC_ENV:-development} +ARG EXPO_PUBLIC_RELEASE_VERSION +ENV EXPO_PUBLIC_RELEASE_VERSION=$EXPO_PUBLIC_RELEASE_VERSION ARG EXPO_PUBLIC_BUNDLE_IDENTIFIER -ENV EXPO_PUBLIC_BUNDLE_IDENTIFIER=${EXPO_PUBLIC_BUNDLE_IDENTIFIER:-dev} - -# The latest git hash of the preview branch on render.com -ARG RENDER_GIT_COMMIT +# If not set by GitHub workflows, we're probably in Render +ENV EXPO_PUBLIC_BUNDLE_IDENTIFIER=${EXPO_PUBLIC_BUNDLE_IDENTIFIER:-$RENDER_GIT_COMMIT} # # Sentry # ARG SENTRY_AUTH_TOKEN ENV SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN:-unknown} -# Will fall back to package.json#version, but this is handled elsewhere -ARG SENTRY_RELEASE -ENV SENTRY_RELEASE=$SENTRY_RELEASE -ARG SENTRY_DIST -# Default to RENDER_GIT_COMMIT if not set by GitHub workflows -ENV SENTRY_DIST=${SENTRY_DIST:-$RENDER_GIT_COMMIT} -ARG SENTRY_DSN -ENV SENTRY_DSN=$SENTRY_DSN +ARG EXPO_PUBLIC_SENTRY_DSN +ENV EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN # # Copy everything into the container @@ -60,13 +60,16 @@ RUN \. "$NVM_DIR/nvm.sh" && \ nvm install $NODE_VERSION && \ nvm use $NODE_VERSION && \ echo "Using bundle identifier: $EXPO_PUBLIC_BUNDLE_IDENTIFIER" && \ + echo "EXPO_PUBLIC_ENV=$EXPO_PUBLIC_ENV" >> .env && \ + echo "EXPO_PUBLIC_RELEASE_VERSION=$EXPO_PUBLIC_RELEASE_VERSION" >> .env && \ echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$EXPO_PUBLIC_BUNDLE_IDENTIFIER" >> .env && \ echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \ + echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \ npm install --global yarn && \ yarn && \ yarn intl:build 2>&1 | tee i18n.log && \ if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \ - EXPO_PUBLIC_BUNDLE_IDENTIFIER=$EXPO_PUBLIC_BUNDLE_IDENTIFIER EXPO_PUBLIC_BUNDLE_DATE=$() SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$SENTRY_RELEASE SENTRY_DIST=$SENTRY_DIST SENTRY_DSN=$SENTRY_DSN yarn build-web + SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web # DEBUG RUN find ./bskyweb/static && find ./web-build/static diff --git a/docs/build.md b/docs/build.md index 7817dd0957..87b653bdb9 100644 --- a/docs/build.md +++ b/docs/build.md @@ -89,9 +89,9 @@ If you change `SENTRY_AUTH_TOKEN`, you need to do `yarn prebuild` before running ### Adding bitdrift -Adding bitdirft is NOT required. You can keep `BITDRIFT_API_KEY=` in `.env` which will avoid initializing bitdrift during startup. +Adding bitdrift is NOT required. You can keep `EXPO_PUBLIC_BITDRIFT_API_KEY=` in `.env` which will avoid initializing bitdrift during startup. -However, if you're a part of the Bluesky team and want to enable bitdrift, fill in `BITDRIFT_API_KEY` in your `.env` to enable bitdrift. +However, if you're a part of the Bluesky team and want to enable bitdrift, fill in `EXPO_PUBLIC_BITDRIFT_API_KEY` in your `.env` to enable bitdrift. ### Adding and Updating Locales diff --git a/src/components/PostControls/DiscoverDebug.tsx b/src/components/PostControls/DiscoverDebug.tsx index 796981f0c2..403b50ccab 100644 --- a/src/components/PostControls/DiscoverDebug.tsx +++ b/src/components/PostControls/DiscoverDebug.tsx @@ -2,13 +2,13 @@ import {Pressable} from 'react-native' import * as Clipboard from 'expo-clipboard' import {t} from '@lingui/macro' -import {IS_INTERNAL} from '#/lib/app-info' import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' import {useGate} from '#/lib/statsig/statsig' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Text} from '#/components/Typography' +import {IS_INTERNAL} from '#/env' export function DiscoverDebug({ feedContext, diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index f0ef9ed055..ecc3d01741 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -17,7 +17,6 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' -import {IS_INTERNAL} from '#/lib/app-info' import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' import {useOpenLink} from '#/lib/hooks/useOpenLink' import {getCurrentRoute} from '#/lib/routes/helpers' @@ -83,6 +82,7 @@ import { useReportDialogControl, } from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' +import {IS_INTERNAL} from '#/env' import * as bsky from '#/types/bsky' let PostMenuItems = ({ diff --git a/src/env.ts b/src/env.ts deleted file mode 100644 index 32ce70670b..0000000000 --- a/src/env.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const LOG_DEBUG = process.env.EXPO_PUBLIC_LOG_DEBUG || '' -export const LOG_LEVEL = (process.env.EXPO_PUBLIC_LOG_LEVEL || 'info') as - | 'debug' - | 'info' - | 'warn' - | 'error' diff --git a/src/env/common.ts b/src/env/common.ts new file mode 100644 index 0000000000..e68e9fab80 --- /dev/null +++ b/src/env/common.ts @@ -0,0 +1,79 @@ +import {type Did} from '@atproto/api' + +import packageJson from '#/../package.json' + +/** + * The semver version of the app, as defined in `package.json.` + * + * N.B. The fallback is needed for Render.com deployments + */ +export const RELEASE_VERSION: string = + process.env.EXPO_PUBLIC_RELEASE_VERSION || packageJson.version + +/** + * The env the app is running in e.g. development, testflight, production + */ +export const ENV: string = process.env.EXPO_PUBLIC_ENV + +/** + * Indicates whether the app is running in TestFlight + */ +export const IS_TESTFLIGHT = ENV === 'testflight' + +/** + * Indicates whether the app is __DEV__ + */ +export const IS_DEV = __DEV__ + +/** + * Indicates whether the app is __DEV__ or TestFlight + */ +export const IS_INTERNAL = IS_DEV || IS_TESTFLIGHT + +/** + * The commit hash that the current bundle was made from. The user can + * see the commit hash in the app's settings along with the other version info. + * Useful for debugging/reporting. + */ +export const BUNDLE_IDENTIFIER: string = + process.env.EXPO_PUBLIC_BUNDLE_IDENTIFIER || 'dev' + +/** + * This will always be in the format of YYMMDDHH, so that it always increases + * for each build. This should only be used for StatSig reporting and shouldn't + * be used to identify a specific bundle. + */ +export const BUNDLE_DATE: number = !process.env.EXPO_PUBLIC_BUNDLE_DATE + ? 0 + : Number(process.env.EXPO_PUBLIC_BUNDLE_DATE) + +/** + * The log level for the app. + */ +export const LOG_LEVEL = (process.env.EXPO_PUBLIC_LOG_LEVEL || 'info') as + | 'debug' + | 'info' + | 'warn' + | 'error' + +/** + * Enable debug logs for specific logger instances + */ +export const LOG_DEBUG: string = process.env.EXPO_PUBLIC_LOG_DEBUG || '' + +/** + * The DID of the chat service to proxy to + */ +export const CHAT_PROXY_DID: Did = + process.env.EXPO_PUBLIC_CHAT_PROXY_DID || 'did:web:api.bsky.chat' + +/** + * Sentry DSN for telemetry + */ +export const SENTRY_DSN: string | undefined = process.env.EXPO_PUBLIC_SENTRY_DSN + +/** + * Bitdrift API key. If undefined, Bitdrift should be disabled. + */ +export const BITDRIFT_API_KEY: string | undefined = + process.env.EXPO_PUBLIC_BITDRIFT_API_KEY diff --git a/src/env/index.ts b/src/env/index.ts new file mode 100644 index 0000000000..8558c55b5d --- /dev/null +++ b/src/env/index.ts @@ -0,0 +1,19 @@ +import {nativeBuildVersion} from 'expo-application' + +import {BUNDLE_IDENTIFIER, IS_TESTFLIGHT, RELEASE_VERSION} from '#/env/common' + +export * from '#/env/common' + +/** + * The semver version of the app, specified in our `package.json`.file. On + * iOs/Android, the native build version is appended to the semver version, so + * that it can be used to identify a specific build. + */ +export const APP_VERSION = `${RELEASE_VERSION}.${nativeBuildVersion}` + +/** + * The short commit hash and environment of the current bundle. + */ +export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${ + __DEV__ ? 'dev' : IS_TESTFLIGHT ? 'tf' : 'prod' +})` diff --git a/src/env/index.web.ts b/src/env/index.web.ts new file mode 100644 index 0000000000..66087749b7 --- /dev/null +++ b/src/env/index.web.ts @@ -0,0 +1,15 @@ +import {BUNDLE_IDENTIFIER, RELEASE_VERSION} from '#/env/common' + +export * from '#/env/common' + +/** + * The semver version of the app, specified in our `package.json`.file. On + * iOs/Android, the native build version is appended to the semver version, so + * that it can be used to identify a specific build. + */ +export const APP_VERSION = RELEASE_VERSION + +/** + * The short commit hash and environment of the current bundle. + */ +export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${__DEV__ ? 'dev' : 'prod'})` diff --git a/src/lib/app-info.ts b/src/lib/app-info.ts deleted file mode 100644 index 0749087eae..0000000000 --- a/src/lib/app-info.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {nativeApplicationVersion, nativeBuildVersion} from 'expo-application' - -export const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight' -export const IS_INTERNAL = __DEV__ || IS_TESTFLIGHT - -// This is the commit hash that the current bundle was made from. The user can see the commit hash in the app's settings -// along with the other version info. Useful for debugging/reporting. -export const BUNDLE_IDENTIFIER = process.env.EXPO_PUBLIC_BUNDLE_IDENTIFIER ?? '' - -// This will always be in the format of YYMMDD, so that it always increases for each build. This should only be used -// for Statsig reporting and shouldn't be used to identify a specific bundle. -export const BUNDLE_DATE = - IS_TESTFLIGHT || __DEV__ ? 0 : Number(process.env.EXPO_PUBLIC_BUNDLE_DATE) - -export const appVersion = `${nativeApplicationVersion}.${nativeBuildVersion}` -export const bundleInfo = `${BUNDLE_IDENTIFIER} (${ - __DEV__ ? 'dev' : IS_TESTFLIGHT ? 'tf' : 'prod' -})` diff --git a/src/lib/app-info.web.ts b/src/lib/app-info.web.ts deleted file mode 100644 index 1530d99769..0000000000 --- a/src/lib/app-info.web.ts +++ /dev/null @@ -1,18 +0,0 @@ -import packageDotJson from '../../package.json' - -export const IS_TESTFLIGHT = false -export const IS_INTERNAL = __DEV__ - -// This is the commit hash that the current bundle was made from. The user can see the commit hash in the app's settings -// along with the other version info. Useful for debugging/reporting. -export const BUNDLE_IDENTIFIER = - process.env.EXPO_PUBLIC_BUNDLE_IDENTIFIER ?? 'dev' - -// This will always be in the format of YYMMDD, so that it always increases for each build. This should only be used -// for Statsig reporting and shouldn't be used to identify a specific bundle. -export const BUNDLE_DATE = __DEV__ - ? 0 - : Number(process.env.EXPO_PUBLIC_BUNDLE_DATE) - -export const appVersion = packageDotJson.version -export const bundleInfo = `${BUNDLE_IDENTIFIER} (${__DEV__ ? 'dev' : 'prod'})` diff --git a/src/lib/hooks/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts index 864d5d6970..ba46b60552 100644 --- a/src/lib/hooks/useOTAUpdates.ts +++ b/src/lib/hooks/useOTAUpdates.ts @@ -10,9 +10,9 @@ import { useUpdates, } from 'expo-updates' -import {IS_TESTFLIGHT} from '#/lib/app-info' import {logger} from '#/logger' import {isIOS} from '#/platform/detection' +import {IS_TESTFLIGHT} from '#/env' const MINIMUM_MINIMIZE_TIME = 15 * 60e3 diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index f2d3ffca9b..1091c82e04 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -3,12 +3,11 @@ import {Platform} from 'react-native' import {AppState, type AppStateStatus} from 'react-native' import {Statsig, StatsigProvider} from 'statsig-react-native-expo' -import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info' import {logger} from '#/logger' import {type MetricEvents} from '#/logger/metrics' import {isWeb} from '#/platform/detection' import * as persisted from '#/state/persisted' -import packageDotJson from '../../../package.json' +import * as env from '#/env' import {useSession} from '../../state/session' import {timeout} from '../async/timeout' import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback' @@ -49,12 +48,11 @@ export type {MetricEvents as LogEvents} function createStatsigOptions(prefetchUsers: StatsigUser[]) { return { environment: { - tier: - process.env.NODE_ENV === 'development' - ? 'development' - : IS_TESTFLIGHT - ? 'staging' - : 'production', + tier: env.IS_DEV + ? 'development' + : env.IS_TESTFLIGHT + ? 'staging' + : 'production', }, // Don't block on waiting for network. The fetched config will kick in on next load. // This ensures the UI is always consistent and doesn't update mid-session. @@ -212,9 +210,9 @@ function toStatsigUser(did: string | undefined): StatsigUser { refSrc, refUrl, platform: Platform.OS as 'ios' | 'android' | 'web', - appVersion: packageDotJson.version, - bundleIdentifier: BUNDLE_IDENTIFIER, - bundleDate: BUNDLE_DATE, + appVersion: env.RELEASE_VERSION, + bundleIdentifier: env.BUNDLE_IDENTIFIER, + bundleDate: env.BUNDLE_DATE, appLanguage: languagePrefs.appLanguage, contentLanguages: languagePrefs.contentLanguages, }, diff --git a/src/logger/bitdrift/setup/index.ts b/src/logger/bitdrift/setup/index.ts index d6af3fe24b..dd2560acc6 100644 --- a/src/logger/bitdrift/setup/index.ts +++ b/src/logger/bitdrift/setup/index.ts @@ -2,8 +2,7 @@ import {init, SessionStrategy} from '@bitdrift/react-native' import {Statsig} from 'statsig-react-native-expo' import {initPromise} from '#/lib/statsig/statsig' - -const BITDRIFT_API_KEY = process.env.BITDRIFT_API_KEY +import {BITDRIFT_API_KEY} from '#/env' initPromise.then(() => { let isEnabled = false diff --git a/src/logger/index.ts b/src/logger/index.ts index e7aaf666a0..998d025818 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -14,9 +14,10 @@ import { } from '#/logger/types' import {enabledLogLevels} from '#/logger/util' import {isNative} from '#/platform/detection' +import {ENV} from '#/env' const TRANSPORTS: Transport[] = (function configureTransports() { - switch (process.env.NODE_ENV) { + switch (ENV) { case 'production': { return [sentryTransport, isNative && bitdriftTransport].filter( Boolean, diff --git a/src/logger/sentry/setup/index.ts b/src/logger/sentry/setup/index.ts index f05a7fc833..d062f05d2b 100644 --- a/src/logger/sentry/setup/index.ts +++ b/src/logger/sentry/setup/index.ts @@ -1,32 +1,15 @@ -/** - * Importing these separately from `platform/detection` and `lib/app-info` to - * avoid future conflicts and/or circular deps - */ - import {init} from '@sentry/react-native' -import pkgJson from '#/../package.json' - -/** - * Examples: - * - `dev` - * - `1.99.0` - */ -const release = process.env.SENTRY_RELEASE || pkgJson.version - -/** - * The latest deployed commit hash - */ -const dist = process.env.SENTRY_DIST || 'dev' +import * as env from '#/env' init({ - enabled: !__DEV__ && !!process.env.SENTRY_DSN, + enabled: !env.IS_DEV && !!env.SENTRY_DSN, autoSessionTracking: false, - dsn: process.env.SENTRY_DSN, + dsn: env.SENTRY_DSN, debug: false, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production - environment: process.env.NODE_ENV, - dist, - release, + environment: env.ENV, + dist: env.BUNDLE_IDENTIFIER, + release: env.RELEASE_VERSION, ignoreErrors: [ /* * Unknown internals errors diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx index 0ce127ff3f..e48841d9f0 100644 --- a/src/screens/Settings/AboutSettings.tsx +++ b/src/screens/Settings/AboutSettings.tsx @@ -9,7 +9,6 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useMutation} from '@tanstack/react-query' import {Statsig} from 'statsig-react-native-expo' -import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info' import {STATUS_PAGE_URL} from '#/lib/constants' import {type CommonNavigatorParams} from '#/lib/routes/types' import {isAndroid, isIOS, isNative} from '#/platform/detection' @@ -23,6 +22,7 @@ import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/i import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' +import * as env from '#/env' import {useDemoMode} from '#/storage/hooks/demo-mode' import {useDevMode} from '#/storage/hooks/dev-mode' import {OTAInfo} from './components/OTAInfo' @@ -123,7 +123,7 @@ export function AboutSettingsScreen({}: Props) { )} { const newDevModeEnabled = !devModeEnabled @@ -146,15 +146,15 @@ export function AboutSettingsScreen({}: Props) { }} onPress={() => { setStringAsync( - `Build version: ${appVersion}; Bundle info: ${bundleInfo}; Bundle date: ${BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}; Anonymous ID: ${stableID}`, + `Build version: ${env.APP_VERSION}; Bundle info: ${env.APP_METADATA}; Bundle date: ${env.BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}; Anonymous ID: ${stableID}`, ) Toast.show(_(msg`Copied build version to clipboard`)) }}> - Version {appVersion} + Version {env.APP_VERSION} - {bundleInfo} + {env.APP_METADATA} {devModeEnabled && ( <> diff --git a/src/screens/Settings/AppIconSettings/index.tsx b/src/screens/Settings/AppIconSettings/index.tsx index 954bac68a5..799873c2d9 100644 --- a/src/screens/Settings/AppIconSettings/index.tsx +++ b/src/screens/Settings/AppIconSettings/index.tsx @@ -3,20 +3,20 @@ import {Alert, View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' -import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {type NativeStackScreenProps} from '@react-navigation/native-stack' -import {IS_INTERNAL} from '#/lib/app-info' import {PressableScale} from '#/lib/custom-animations/PressableScale' -import {CommonNavigatorParams} from '#/lib/routes/types' +import {type CommonNavigatorParams} from '#/lib/routes/types' import {useGate} from '#/lib/statsig/statsig' import {isAndroid} from '#/platform/detection' import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' -import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' +import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' import {atoms as a, useTheme} from '#/alf' import * as Toggle from '#/components/forms/Toggle' import * as Layout from '#/components/Layout' import {Text} from '#/components/Typography' +import {IS_INTERNAL} from '#/env' type Props = NativeStackScreenProps export function AppIconSettingsScreen({}: Props) { diff --git a/src/screens/Settings/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx index d0158aaa84..492d6d172b 100644 --- a/src/screens/Settings/AppearanceSettings.tsx +++ b/src/screens/Settings/AppearanceSettings.tsx @@ -8,7 +8,6 @@ import Animated, { import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {IS_INTERNAL} from '#/lib/app-info' import { type CommonNavigatorParams, type NativeStackScreenProps, @@ -26,6 +25,7 @@ import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/T import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase' import * as Layout from '#/components/Layout' import {Text} from '#/components/Typography' +import {IS_INTERNAL} from '#/env' import * as SettingsList from './components/SettingsList' type Props = NativeStackScreenProps diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index b712c054cb..719bbf9a29 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -9,7 +9,6 @@ import {useNavigation} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useActorStatus} from '#/lib/actor-status' -import {IS_INTERNAL} from '#/lib/app-info' import {HELP_DESK_URL} from '#/lib/constants' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {useApplyPullRequestOTAUpdate} from '#/lib/hooks/useOTAUpdates' @@ -66,6 +65,7 @@ import { shouldShowVerificationCheckButton, VerificationCheckButton, } from '#/components/verification/VerificationCheckButton' +import {IS_INTERNAL} from '#/env' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' type Props = NativeStackScreenProps diff --git a/src/state/queries/messages/const.ts b/src/state/queries/messages/const.ts index 7642c5d7b2..1c5519a633 100644 --- a/src/state/queries/messages/const.ts +++ b/src/state/queries/messages/const.ts @@ -1,3 +1,5 @@ +import {CHAT_PROXY_DID} from '#/env' + export const DM_SERVICE_HEADERS = { - 'atproto-proxy': 'did:web:api.bsky.chat#bsky_chat', + 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, } diff --git a/src/state/session/logging.ts b/src/state/session/logging.ts index 98de5a3964..bf847f08f5 100644 --- a/src/state/session/logging.ts +++ b/src/state/session/logging.ts @@ -1,11 +1,11 @@ -import {AtpSessionData, AtpSessionEvent} from '@atproto/api' +import {type AtpSessionData, type AtpSessionEvent} from '@atproto/api' import {sha256} from 'js-sha256' import {Statsig} from 'statsig-react-native-expo' -import {IS_INTERNAL} from '#/lib/app-info' -import {Schema} from '../persisted' -import {Action, State} from './reducer' -import {SessionAccount} from './types' +import {IS_INTERNAL} from '#/env' +import {type Schema} from '../persisted' +import {type Action, type State} from './reducer' +import {type SessionAccount} from './types' type Reducer = (state: State, action: Action) => State diff --git a/webpack.config.js b/webpack.config.js index 9a238e5493..7f58a7448a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -53,7 +53,7 @@ module.exports = async function (env, argv) { project: 'app', authToken: process.env.SENTRY_AUTH_TOKEN, release: { - // env is undefined for Render.com builds, fall back + // fallback needed for Render.com deployments name: process.env.SENTRY_RELEASE || version, dist: process.env.SENTRY_DIST, }, From ef167eaca01426cd8087756d199da687cf36d1bb Mon Sep 17 00:00:00 2001 From: Jaz Volpert Date: Wed, 30 Jul 2025 17:41:18 +0000 Subject: [PATCH 03/22] build --- .github/workflows/build-and-push-bskyweb-aws.yaml | 1 + .github/workflows/build-and-push-embedr-aws.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 6d573b0f71..5160bc2431 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + - go-124 env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} diff --git a/.github/workflows/build-and-push-embedr-aws.yaml b/.github/workflows/build-and-push-embedr-aws.yaml index 23d1538fe5..07a6d616a8 100644 --- a/.github/workflows/build-and-push-embedr-aws.yaml +++ b/.github/workflows/build-and-push-embedr-aws.yaml @@ -3,6 +3,7 @@ on: push: branches: - main + - go-124 env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} From 324ebdf2cb954e50bed9d220c653d296afc93d64 Mon Sep 17 00:00:00 2001 From: Jaz Volpert Date: Wed, 30 Jul 2025 17:57:56 +0000 Subject: [PATCH 04/22] Cleanup workflows --- .github/workflows/build-and-push-bskyweb-aws.yaml | 1 - .github/workflows/build-and-push-embedr-aws.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 5160bc2431..6d573b0f71 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -4,7 +4,6 @@ on: push: branches: - main - - go-124 env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} diff --git a/.github/workflows/build-and-push-embedr-aws.yaml b/.github/workflows/build-and-push-embedr-aws.yaml index 07a6d616a8..23d1538fe5 100644 --- a/.github/workflows/build-and-push-embedr-aws.yaml +++ b/.github/workflows/build-and-push-embedr-aws.yaml @@ -3,7 +3,6 @@ on: push: branches: - main - - go-124 env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} From fade51e9eba84cfba1a8797aafc4958ab3764deb Mon Sep 17 00:00:00 2001 From: hailey Date: Wed, 30 Jul 2025 11:00:14 -0700 Subject: [PATCH 05/22] Rework variable naming, logic for rendering chat requests (#8737) --- src/screens/Messages/ChatList.tsx | 56 +++++++++---------- .../Messages/components/InboxPreview.tsx | 6 +- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index c37a025cc9..3454464648 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -55,7 +55,7 @@ type ListItem = function renderItem({item}: {item: ListItem}) { switch (item.type) { case 'INBOX': - return + return case 'CONVERSATION': return } @@ -140,22 +140,24 @@ export function MessagesScreenInner({navigation, route}: Props) { const leftConvos = useLeftConvos() - const inboxPreviewConvos = useMemo(() => { - const inbox = - inboxData?.pages - .flatMap(page => page.convos) - .filter( - convo => - !leftConvos.includes(convo.id) && - !convo.muted && - convo.unreadCount > 0 && - convo.members.every(member => member.handle !== 'missing.invalid'), - ) ?? [] + const inboxAllConvos = + inboxData?.pages + .flatMap(page => page.convos) + .filter( + convo => + !leftConvos.includes(convo.id) && + !convo.muted && + convo.members.every(member => member.handle !== 'missing.invalid'), + ) ?? [] + const hasInboxConvos = inboxAllConvos?.length > 0 - return inbox - .map(x => x.members.find(y => y.did !== currentAccount?.did)) - .filter(x => !!x) - }, [inboxData, leftConvos, currentAccount?.did]) + const inboxUnreadConvos = inboxAllConvos.filter( + convo => convo.unreadCount > 0, + ) + + const inboxUnreadConvoMembers = inboxUnreadConvos + .map(x => x.members.find(y => y.did !== currentAccount?.did)) + .filter(x => !!x) const conversations = useMemo(() => { if (data?.pages) { @@ -164,15 +166,13 @@ export function MessagesScreenInner({navigation, route}: Props) { // filter out convos that are actively being left .filter(convo => !leftConvos.includes(convo.id)) - const hasInboxRequests = inboxPreviewConvos?.length > 0 - return [ - ...(hasInboxRequests + ...(hasInboxConvos ? [ { type: 'INBOX' as const, - count: inboxPreviewConvos.length, - profiles: inboxPreviewConvos.slice(0, 3), + count: inboxUnreadConvoMembers.length, + profiles: inboxUnreadConvoMembers.slice(0, 3), }, ] : []), @@ -182,7 +182,7 @@ export function MessagesScreenInner({navigation, route}: Props) { ] satisfies ListItem[] } return [] - }, [data, leftConvos, inboxPreviewConvos]) + }, [data, leftConvos, hasInboxConvos, inboxUnreadConvoMembers]) const onRefresh = useCallback(async () => { setIsPTRing(true) @@ -231,21 +231,17 @@ export function MessagesScreenInner({navigation, route}: Props) { // NOTE(APiligrim) // Show empty state only if there are no conversations at all - const actualConversations = conversations.filter( + const activeConversations = conversations.filter( item => item.type === 'CONVERSATION', ) - const hasInboxRequests = inboxPreviewConvos?.length > 0 - if (actualConversations.length === 0) { + if (activeConversations.length === 0) { return (
- {hasInboxRequests && ( - + {!isLoading && hasInboxConvos && ( + )} {isLoading ? ( diff --git a/src/screens/Messages/components/InboxPreview.tsx b/src/screens/Messages/components/InboxPreview.tsx index fe2803522e..6e1b028223 100644 --- a/src/screens/Messages/components/InboxPreview.tsx +++ b/src/screens/Messages/components/InboxPreview.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {ChatBskyActorDefs} from '@atproto/api' +import {type ChatBskyActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -12,10 +12,8 @@ import {Link} from '#/components/Link' export function InboxPreview({ profiles, -}: // count, -{ +}: { profiles: ChatBskyActorDefs.ProfileViewBasic[] - count: number }) { const {_} = useLingui() const t = useTheme() From 25e60548c41949e7ad45d043cbdf8b8cfa3e036a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 30 Jul 2025 21:05:45 +0300 Subject: [PATCH 06/22] Mark old components as deprecated (#8747) * mark a bunch of stuff as deprecated * mark s as deprecated (serverence reference????) * rm type import --- src/lib/ThemeContext.tsx | 10 +++-- src/lib/hooks/usePalette.ts | 12 +++++- src/lib/styles.ts | 15 +++++++- src/state/modals/index.tsx | 9 +++++ src/view/com/util/Link.tsx | 9 +++++ src/view/com/util/ViewHeader.tsx | 2 +- src/view/com/util/forms/Button.tsx | 18 +++++---- src/view/com/util/forms/DropdownButton.tsx | 19 ++++++---- src/view/com/util/forms/NativeDropdown.tsx | 38 +++++++++++++++++-- .../com/util/forms/NativeDropdown.web.tsx | 3 ++ src/view/com/util/forms/ToggleButton.tsx | 15 ++++++-- src/view/com/util/text/Text.tsx | 8 ++-- 12 files changed, 124 insertions(+), 34 deletions(-) diff --git a/src/lib/ThemeContext.tsx b/src/lib/ThemeContext.tsx index e4cebfcb2b..c00d26606f 100644 --- a/src/lib/ThemeContext.tsx +++ b/src/lib/ThemeContext.tsx @@ -1,7 +1,8 @@ -import React, {createContext, ReactNode, useContext} from 'react' -import {TextStyle, ViewStyle} from 'react-native' +import {type ReactNode} from 'react' +import {createContext, useContext} from 'react' +import {type TextStyle, type ViewStyle} from 'react-native' -import {ThemeName} from '#/alf/types' +import {type ThemeName} from '#/alf/types' import {darkTheme, defaultTheme, dimTheme} from './themes' export type ColorScheme = 'light' | 'dark' @@ -29,6 +30,9 @@ export type Palette = Record export type ShapeName = 'button' | 'bigButton' | 'smallButton' export type Shapes = Record +/** + * @deprecated use typography atoms from `#/alf` + */ export type TypographyVariant = | '2xl-thin' | '2xl' diff --git a/src/lib/hooks/usePalette.ts b/src/lib/hooks/usePalette.ts index 2530642f60..db226fba65 100644 --- a/src/lib/hooks/usePalette.ts +++ b/src/lib/hooks/usePalette.ts @@ -1,7 +1,11 @@ import {useMemo} from 'react' -import {TextStyle, ViewStyle} from 'react-native' +import {type TextStyle, type ViewStyle} from 'react-native' -import {PaletteColor, PaletteColorName, useTheme} from '../ThemeContext' +import { + type PaletteColor, + type PaletteColorName, + useTheme, +} from '../ThemeContext' export interface UsePaletteValue { colors: PaletteColor @@ -16,6 +20,10 @@ export interface UsePaletteValue { link: TextStyle icon: TextStyle } + +/** + * @deprecated use `useTheme` from `#/alf` + */ export function usePalette(color: PaletteColorName): UsePaletteValue { const theme = useTheme() return useMemo(() => { diff --git a/src/lib/styles.ts b/src/lib/styles.ts index 55fb1a844b..13b8c2bf39 100644 --- a/src/lib/styles.ts +++ b/src/lib/styles.ts @@ -1,9 +1,17 @@ -import {Dimensions, StyleProp, StyleSheet, TextStyle} from 'react-native' +import { + Dimensions, + type StyleProp, + StyleSheet, + type TextStyle, +} from 'react-native' import {isWeb} from '#/platform/detection' -import {Theme, TypographyVariant} from './ThemeContext' +import {type Theme, type TypographyVariant} from './ThemeContext' // 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest +/** + * @deprecated use ALF colors instead + */ export const colors = { white: '#ffffff', black: '#000000', @@ -63,6 +71,9 @@ export const gradients = { blueDark: {start: '#5F45E0', end: colors.blue3}, // avis, banner } +/** + * @deprecated use atoms from `#/alf` + */ export const s = StyleSheet.create({ // helpers footerSpacer: {height: 100}, diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index a2cc637450..7b6f5d7b31 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -43,6 +43,9 @@ export interface ChangePasswordModal { name: 'change-password' } +/** + * @deprecated DO NOT ADD NEW MODALS + */ export type Modal = // Account | DeleteAccountModal @@ -125,10 +128,16 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ) } +/** + * @deprecated use the dialog system from `#/components/Dialog.tsx` + */ export function useModals() { return React.useContext(ModalContext) } +/** + * @deprecated use the dialog system from `#/components/Dialog.tsx` + */ export function useModalControls() { return React.useContext(ModalControlContext) } diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index 496b771821..33f9b41d8f 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -56,6 +56,9 @@ interface Props extends React.ComponentProps { onBeforePress?: () => void } +/** + * @deprecated use Link from `#/components/Link.tsx` instead + */ export const Link = memo(function Link({ testID, style, @@ -156,6 +159,9 @@ export const Link = memo(function Link({ ) }) +/** + * @deprecated use InlineLinkText from `#/components/Link.tsx` instead + */ export const TextLink = memo(function TextLink({ testID, type = 'md', @@ -301,6 +307,9 @@ interface TextLinkOnWebOnlyProps extends TextProps { onPointerEnter?: () => void anchorNoUnderline?: boolean } +/** + * @deprecated use WebOnlyInlineLinkText from `#/components/Link.tsx` instead + */ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ testID, type = 'md', diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 2d413f7825..999ce52bda 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -3,7 +3,7 @@ import {Header} from '#/components/Layout' /** * Legacy ViewHeader component. Use Layout.Header going forward. * - * @deprecated + * @deprecated use `Layout.Header` from `#/components/Layout.tsx` */ export function ViewHeader({ title, diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx index 96b49a0039..24d478fe54 100644 --- a/src/view/com/util/forms/Button.tsx +++ b/src/view/com/util/forms/Button.tsx @@ -1,16 +1,16 @@ import React from 'react' import { ActivityIndicator, - GestureResponderEvent, - NativeSyntheticEvent, - NativeTouchEvent, + type GestureResponderEvent, + type NativeSyntheticEvent, + type NativeTouchEvent, Pressable, - PressableStateCallbackType, - StyleProp, + type PressableStateCallbackType, + type StyleProp, StyleSheet, - TextStyle, + type TextStyle, View, - ViewStyle, + type ViewStyle, } from 'react-native' import {choose} from '#/lib/functions' @@ -37,7 +37,9 @@ declare module 'react-native' { } } -// TODO: Enforce that button always has a label +/** + * @deprecated use Button from `#/components/Button.tsx` instead + */ export function Button({ type = 'primary', label, diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index a061edf2ef..e20dadb490 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -1,22 +1,24 @@ -import React, {PropsWithChildren, useMemo, useRef} from 'react' +import {type PropsWithChildren} from 'react' +import {useMemo, useRef} from 'react' import { Dimensions, - GestureResponderEvent, - Insets, - StyleProp, + type GestureResponderEvent, + type Insets, + type StyleProp, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, useWindowDimensions, View, - ViewStyle, + type ViewStyle, } from 'react-native' import Animated, {FadeIn, FadeInDown, FadeInUp} from 'react-native-reanimated' import RootSiblings from 'react-native-root-siblings' -import {IconProp} from '@fortawesome/fontawesome-svg-core' +import {type IconProp} from '@fortawesome/fontawesome-svg-core' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import type React from 'react' import {HITSLOP_10} from '#/lib/constants' import {usePalette} from '#/lib/hooks/usePalette' @@ -26,7 +28,7 @@ import {isWeb} from '#/platform/detection' import {native} from '#/alf' import {FullWindowOverlay} from '#/components/FullWindowOverlay' import {Text} from '../text/Text' -import {Button, ButtonType} from './Button' +import {Button, type ButtonType} from './Button' const ESTIMATED_BTN_HEIGHT = 50 const ESTIMATED_SEP_HEIGHT = 16 @@ -70,6 +72,9 @@ interface DropdownButtonProps { accessibilityHint?: string } +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export function DropdownButton({ testID, type = 'bare', diff --git a/src/view/com/util/forms/NativeDropdown.tsx b/src/view/com/util/forms/NativeDropdown.tsx index 8fc9be6da1..5e2089d292 100644 --- a/src/view/com/util/forms/NativeDropdown.tsx +++ b/src/view/com/util/forms/NativeDropdown.tsx @@ -1,9 +1,15 @@ import React from 'react' -import {Platform, Pressable, StyleSheet, View, ViewStyle} from 'react-native' -import {IconProp} from '@fortawesome/fontawesome-svg-core' +import { + Platform, + Pressable, + StyleSheet, + View, + type ViewStyle, +} from 'react-native' +import {type IconProp} from '@fortawesome/fontawesome-svg-core' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import * as DropdownMenu from 'zeego/dropdown-menu' -import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' +import {type MenuItemCommonProps} from 'zeego/lib/typescript/menu' import {usePalette} from '#/lib/hooks/usePalette' import {useTheme} from '#/lib/ThemeContext' @@ -12,8 +18,14 @@ import {Portal} from '#/components/Portal' // Custom Dropdown Menu Components // == +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuRoot = DropdownMenu.Root // export const DropdownMenuTrigger = DropdownMenu.Trigger +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuContent = DropdownMenu.Content type TriggerProps = Omit< @@ -25,6 +37,9 @@ type TriggerProps = Omit< accessibilityLabel?: string accessibilityHint?: string }> +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuTrigger = DropdownMenu.create( (props: TriggerProps) => { const theme = useTheme() @@ -59,6 +74,9 @@ export const DropdownMenuTrigger = DropdownMenu.create( ) type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']> +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuItem = DropdownMenu.create( (props: ItemProps & {testID?: string}) => { const theme = useTheme() @@ -84,6 +102,9 @@ export const DropdownMenuItem = DropdownMenu.create( ) type TitleProps = React.ComponentProps<(typeof DropdownMenu)['ItemTitle']> +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuItemTitle = DropdownMenu.create( (props: TitleProps) => { const pal = usePalette('default') @@ -98,11 +119,17 @@ export const DropdownMenuItemTitle = DropdownMenu.create( ) type IconProps = React.ComponentProps<(typeof DropdownMenu)['ItemIcon']> +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuItemIcon = DropdownMenu.create((props: IconProps) => { return }, 'ItemIcon') type SeparatorProps = React.ComponentProps<(typeof DropdownMenu)['Separator']> +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export const DropdownMenuSeparator = DropdownMenu.create( (props: SeparatorProps) => { const pal = usePalette('default') @@ -142,11 +169,14 @@ type Props = { triggerStyle?: ViewStyle } -/* The `NativeDropdown` function uses native iOS and Android dropdown menus. +/** + * The `NativeDropdown` function uses native iOS and Android dropdown menus. * It also creates a animated custom dropdown for web that uses * Radix UI primitives under the hood * @prop {DropdownItem[]} items - An array of dropdown items * @prop {React.ReactNode} children - A custom dropdown trigger + * + * @deprecated use Menu from `#/components/Menu.tsx` instead */ export function NativeDropdown({ items, diff --git a/src/view/com/util/forms/NativeDropdown.web.tsx b/src/view/com/util/forms/NativeDropdown.web.tsx index cab7bac510..37df48da38 100644 --- a/src/view/com/util/forms/NativeDropdown.web.tsx +++ b/src/view/com/util/forms/NativeDropdown.web.tsx @@ -63,6 +63,9 @@ type Props = { triggerStyle?: ViewStyle } +/** + * @deprecated use Menu from `#/components/Menu.tsx` instead + */ export function NativeDropdown({ items, children, diff --git a/src/view/com/util/forms/ToggleButton.tsx b/src/view/com/util/forms/ToggleButton.tsx index 31222aafe8..ca9d9fb493 100644 --- a/src/view/com/util/forms/ToggleButton.tsx +++ b/src/view/com/util/forms/ToggleButton.tsx @@ -1,12 +1,21 @@ -import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' +import { + type StyleProp, + StyleSheet, + type TextStyle, + View, + type ViewStyle, +} from 'react-native' import {choose} from '#/lib/functions' import {colors} from '#/lib/styles' import {useTheme} from '#/lib/ThemeContext' -import {TypographyVariant} from '#/lib/ThemeContext' +import {type TypographyVariant} from '#/lib/ThemeContext' import {Text} from '../text/Text' -import {Button, ButtonType} from './Button' +import {Button, type ButtonType} from './Button' +/** + * @deprecated use Toggle from `#/components/form/Toggle.tsx` instead + */ export function ToggleButton({ testID, type = 'default-light', diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx index 785cef09ad..1e49605f49 100644 --- a/src/view/com/util/text/Text.tsx +++ b/src/view/com/util/text/Text.tsx @@ -1,16 +1,16 @@ import React from 'react' -import {StyleSheet, TextProps} from 'react-native' +import {StyleSheet, type TextProps} from 'react-native' import {UITextView} from 'react-native-uitextview' import {lh, s} from '#/lib/styles' -import {TypographyVariant, useTheme} from '#/lib/ThemeContext' +import {type TypographyVariant, useTheme} from '#/lib/ThemeContext' import {logger} from '#/logger' import {isIOS, isWeb} from '#/platform/detection' import {applyFonts, useAlf} from '#/alf' import { childHasEmoji, renderChildrenWithEmoji, - StringChild, + type StringChild, } from '#/alf/typography' export type CustomTextProps = Omit & { @@ -32,7 +32,7 @@ export type CustomTextProps = Omit & { export {Text_DEPRECATED as Text} /** - * @deprecated use Text from Typography instead. + * @deprecated use Text from `#/components/Typography.tsx` instead */ function Text_DEPRECATED({ type = 'md', From 3ff42f1595727a40807a0d6876a133c65d6863f2 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 30 Jul 2025 13:56:27 -0500 Subject: [PATCH 07/22] Fix bad substitution (#8752) --- src/env/common.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/env/common.ts b/src/env/common.ts index e68e9fab80..5b902622bc 100644 --- a/src/env/common.ts +++ b/src/env/common.ts @@ -43,9 +43,10 @@ export const BUNDLE_IDENTIFIER: string = * for each build. This should only be used for StatSig reporting and shouldn't * be used to identify a specific bundle. */ -export const BUNDLE_DATE: number = !process.env.EXPO_PUBLIC_BUNDLE_DATE - ? 0 - : Number(process.env.EXPO_PUBLIC_BUNDLE_DATE) +export const BUNDLE_DATE: number = + process.env.EXPO_PUBLIC_BUNDLE_DATE === undefined + ? 0 + : Number(process.env.EXPO_PUBLIC_BUNDLE_DATE) /** * The log level for the app. From 4393a275ae72e535aec41cc5af6f84e8d1e153c3 Mon Sep 17 00:00:00 2001 From: Jaz Volpert Date: Wed, 30 Jul 2025 21:42:26 +0000 Subject: [PATCH 08/22] fix echoprom config --- bskyweb/cmd/embedr/server.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bskyweb/cmd/embedr/server.go b/bskyweb/cmd/embedr/server.go index d8df4276f2..62427e70e7 100644 --- a/bskyweb/cmd/embedr/server.go +++ b/bskyweb/cmd/embedr/server.go @@ -154,7 +154,13 @@ func serve(cctx *cli.Context) error { RedirectCode: http.StatusFound, })) - e.Use(echoprometheus.NewMiddleware("")) + echoprom := echoprometheus.NewMiddlewareWithConfig( + echoprometheus.MiddlewareConfig{ + DoNotUseRequestPathFor404: true, + }, + ) + + e.Use(echoprom) // // configure routes From b25677eb7f30263471f8a4a6a692d48bc6dc51dd Mon Sep 17 00:00:00 2001 From: Jaz Volpert Date: Wed, 30 Jul 2025 21:42:56 +0000 Subject: [PATCH 09/22] build --- .github/workflows/build-and-push-embedr-aws.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-push-embedr-aws.yaml b/.github/workflows/build-and-push-embedr-aws.yaml index 23d1538fe5..a6b2d9c3e0 100644 --- a/.github/workflows/build-and-push-embedr-aws.yaml +++ b/.github/workflows/build-and-push-embedr-aws.yaml @@ -3,6 +3,7 @@ on: push: branches: - main + - echoprom_fix env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} From 85981deb31f5fc4e114c13b8fe6402c003e4c01a Mon Sep 17 00:00:00 2001 From: pfrazee <1270099+pfrazee@users.noreply.github.com> Date: Thu, 31 Jul 2025 02:51:14 +0000 Subject: [PATCH 10/22] Nightly source-language update --- src/locale/locales/en/messages.po | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index 3a5480a5cd..91699294d3 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -1239,7 +1239,7 @@ msgstr "" #: src/components/dms/dialogs/NewChatDialog.tsx:54 #: src/components/dms/MessageProfileButton.tsx:58 -#: src/screens/Messages/ChatList.tsx:362 +#: src/screens/Messages/ChatList.tsx:358 #: src/screens/Messages/Conversation.tsx:228 msgid "Before you can message another user, you must first verify your email." msgstr "" @@ -1663,11 +1663,11 @@ msgid "Chat muted" msgstr "" #: src/Navigation.tsx:558 -#: src/screens/Messages/components/InboxPreview.tsx:24 +#: src/screens/Messages/components/InboxPreview.tsx:22 msgid "Chat request inbox" msgstr "" -#: src/screens/Messages/components/InboxPreview.tsx:64 +#: src/screens/Messages/components/InboxPreview.tsx:62 #: src/screens/Messages/Inbox.tsx:56 #: src/screens/Messages/Inbox.tsx:98 msgid "Chat requests" @@ -1675,7 +1675,7 @@ msgstr "" #: src/components/dms/ConvoMenu.tsx:75 #: src/Navigation.tsx:553 -#: src/screens/Messages/ChatList.tsx:371 +#: src/screens/Messages/ChatList.tsx:367 msgid "Chat settings" msgstr "" @@ -1690,8 +1690,8 @@ msgid "Chat unmuted" msgstr "" #: src/screens/Messages/ChatList.tsx:76 -#: src/screens/Messages/ChatList.tsx:387 -#: src/screens/Messages/ChatList.tsx:411 +#: src/screens/Messages/ChatList.tsx:383 +#: src/screens/Messages/ChatList.tsx:407 msgid "Chats" msgstr "" @@ -3339,7 +3339,7 @@ msgstr "" msgid "Failed to delete starter pack" msgstr "" -#: src/screens/Messages/ChatList.tsx:274 +#: src/screens/Messages/ChatList.tsx:270 #: src/screens/Messages/Inbox.tsx:208 msgid "Failed to load conversations" msgstr "" @@ -5300,8 +5300,8 @@ msgid "New {postsCount, plural, one {post} other {posts}} from {firstAuthorName} msgstr "" #: src/components/dms/dialogs/NewChatDialog.tsx:67 -#: src/screens/Messages/ChatList.tsx:394 -#: src/screens/Messages/ChatList.tsx:401 +#: src/screens/Messages/ChatList.tsx:390 +#: src/screens/Messages/ChatList.tsx:397 msgid "New chat" msgstr "" @@ -5592,7 +5592,7 @@ msgstr "" msgid "Note: This post is only visible to logged-in users." msgstr "" -#: src/screens/Messages/ChatList.tsx:295 +#: src/screens/Messages/ChatList.tsx:291 msgid "Nothing here" msgstr "" @@ -5834,7 +5834,7 @@ msgstr "" msgid "Open system log" msgstr "" -#: src/view/com/util/forms/DropdownButton.tsx:162 +#: src/view/com/util/forms/DropdownButton.tsx:167 msgid "Opens {numItems} options" msgstr "" @@ -6688,7 +6688,7 @@ msgstr "" msgid "Reject chat request" msgstr "" -#: src/screens/Messages/ChatList.tsx:278 +#: src/screens/Messages/ChatList.tsx:274 #: src/screens/Messages/Inbox.tsx:212 msgid "Reload conversations" msgstr "" @@ -7158,7 +7158,7 @@ msgstr "" #: src/components/StarterPack/ProfileStarterPacks.tsx:346 #: src/screens/Login/LoginForm.tsx:323 #: src/screens/Login/LoginForm.tsx:330 -#: src/screens/Messages/ChatList.tsx:284 +#: src/screens/Messages/ChatList.tsx:280 #: src/screens/Messages/components/MessageListError.tsx:25 #: src/screens/Messages/Inbox.tsx:218 #: src/screens/Onboarding/StepInterests/index.tsx:217 @@ -7538,7 +7538,7 @@ msgstr "" msgid "Select your preferred notification channels" msgstr "" -#: src/view/com/util/forms/DropdownButton.tsx:297 +#: src/view/com/util/forms/DropdownButton.tsx:302 msgid "Selects option {0} of {numItems}" msgstr "" @@ -8933,7 +8933,7 @@ msgstr "" msgid "Today" msgstr "" -#: src/view/com/util/forms/DropdownButton.tsx:258 +#: src/view/com/util/forms/DropdownButton.tsx:263 msgid "Toggle dropdown" msgstr "" @@ -9517,7 +9517,7 @@ msgstr "" #: src/screens/Settings/AboutSettings.tsx:126 #: src/screens/Settings/AboutSettings.tsx:155 -msgid "Version {appVersion}" +msgid "Version {0}" msgstr "" #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:83 @@ -9901,7 +9901,7 @@ msgid "Who can verify?" msgstr "" #: src/screens/Home/NoFeedsPinned.tsx:79 -#: src/screens/Messages/ChatList.tsx:262 +#: src/screens/Messages/ChatList.tsx:258 #: src/screens/Messages/Inbox.tsx:197 msgid "Whoops!" msgstr "" @@ -10173,7 +10173,7 @@ msgstr "" msgid "You have muted this user" msgstr "" -#: src/screens/Messages/ChatList.tsx:305 +#: src/screens/Messages/ChatList.tsx:301 msgid "You have no conversations yet. Start one!" msgstr "" From b3d207c7b9578314ae0e67ef6f3b8186585156ad Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 31 Jul 2025 07:41:10 -0500 Subject: [PATCH 11/22] Make proper extension of `Button` more clearly defined (#8753) * Remove unecessary color prop from SettingsList LinkItem * Add UninheritableButtonProps to avoid misuse --- src/components/Button.tsx | 12 ++++++++++++ src/screens/Settings/components/SettingsList.tsx | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 42eb648443..22c9ab96d0 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -20,6 +20,18 @@ import {atoms as a, flatten, select, tokens, useTheme} from '#/alf' import {type Props as SVGIconProps} from '#/components/icons/common' import {Text} from '#/components/Typography' +/** + * The `Button` component, and some extensions of it like `Link` are intended + * to be generic and therefore apply no styles by default. These `VariantProps` + * are what control the `Button`'s presentation, and are intended only use cases where the buttons appear as, well, buttons. + * + * If `Button` or an extension of it are used for other compound components, use this property to avoid misuse of these variant props further down the line. + * + * @example + * type MyComponentProps = Omit & {...} + */ +export type UninheritableButtonProps = 'variant' | 'color' | 'size' | 'shape' + export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient' export type ButtonColor = | 'primary' diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index 6d17990478..5720849723 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -124,7 +124,7 @@ export function LinkItem({ contentContainerStyle, chevronColor, ...props -}: LinkProps & { +}: Omit & { contentContainerStyle?: StyleProp destructive?: boolean chevronColor?: string @@ -132,7 +132,7 @@ export function LinkItem({ const t = useTheme() return ( - + {args => ( & { contentContainerStyle?: StyleProp destructive?: boolean }) { From 33e071494881b13696e24b334857e594f29a4b1d Mon Sep 17 00:00:00 2001 From: surfdude29 <149612116+surfdude29@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:40:39 +0100 Subject: [PATCH 12/22] tweak case of labels in SearchResults.tsx (#8759) --- src/screens/Search/SearchResults.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx index b626c93295..4c40684176 100644 --- a/src/screens/Search/SearchResults.tsx +++ b/src/screens/Search/SearchResults.tsx @@ -255,7 +255,7 @@ let SearchScreenPostResults = ({ Sign in @@ -263,7 +263,7 @@ let SearchScreenPostResults = ({ or create an account From 3bcfcba6d8176bac03202b496110915da748b0f1 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 31 Jul 2025 10:15:35 -0500 Subject: [PATCH 13/22] Some toasts cleanup and reorg (#8748) * Reorg * Move animation into css file * Update style comment * Extract core component, use platform-specific wrappers * Pull out platform specific styles * Just move styles into Toast component itself * Rename cleanup * Update API * Add duration optional prop * Add some type docs * add exp eased slide aniamtions * Make toasts full width on mobile web --------- Co-authored-by: Samuel Newman --- src/App.web.tsx | 2 +- src/components/Toast/Toast.tsx | 205 +++++++++++++++++++ src/components/Toast/const.ts | 1 + src/components/Toast/index.e2e.tsx | 5 + src/components/Toast/index.tsx | 197 +++++++++++++++++++ src/components/Toast/index.web.tsx | 107 ++++++++++ src/components/Toast/types.ts | 24 +++ src/style.css | 20 ++ src/view/com/util/Toast.e2e.tsx | 1 - src/view/com/util/Toast.style.tsx | 201 ------------------- src/view/com/util/Toast.tsx | 272 +++++--------------------- src/view/com/util/Toast.web.tsx | 180 ----------------- src/view/screens/Storybook/Toasts.tsx | 188 ++++++++++-------- src/view/screens/Storybook/index.tsx | 3 +- 14 files changed, 709 insertions(+), 697 deletions(-) create mode 100644 src/components/Toast/Toast.tsx create mode 100644 src/components/Toast/const.ts create mode 100644 src/components/Toast/index.e2e.tsx create mode 100644 src/components/Toast/index.tsx create mode 100644 src/components/Toast/index.web.tsx create mode 100644 src/components/Toast/types.ts delete mode 100644 src/view/com/util/Toast.e2e.tsx delete mode 100644 src/view/com/util/Toast.style.tsx delete mode 100644 src/view/com/util/Toast.web.tsx diff --git a/src/App.web.tsx b/src/App.web.tsx index 04de8529ff..1f795cb3e0 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -50,7 +50,6 @@ import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies' import * as Toast from '#/view/com/util/Toast' -import {ToastContainer} from '#/view/com/util/Toast.web' import {Shell} from '#/view/shell/index' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' @@ -61,6 +60,7 @@ import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialo import {Provider as PortalProvider} from '#/components/Portal' import {Provider as ActiveVideoProvider} from '#/components/Post/Embed/VideoEmbed/ActiveVideoWebContext' import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' +import {ToastContainer} from '#/components/Toast' import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder' diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx new file mode 100644 index 0000000000..0dc9d4b079 --- /dev/null +++ b/src/components/Toast/Toast.tsx @@ -0,0 +1,205 @@ +import {createContext, useContext, useMemo} from 'react' +import {View} from 'react-native' + +import {atoms as a, select, useTheme} from '#/alf' +import {Check_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' +import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' +import {type ToastType} from '#/components/Toast/types' +import {Text} from '#/components/Typography' + +type ContextType = { + type: ToastType +} + +export const ICONS = { + default: SuccessIcon, + success: SuccessIcon, + error: ErrorIcon, + warning: WarningIcon, + info: CircleInfo, +} + +const Context = createContext({ + type: 'default', +}) + +export function Toast({ + type, + content, +}: { + type: ToastType + content: React.ReactNode +}) { + const t = useTheme() + const styles = useToastStyles({type}) + const Icon = ICONS[type] + + return ( + ({type}), [type])}> + + + + + {typeof content === 'string' ? ( + {content} + ) : ( + content + )} + + + + ) +} + +export function ToastText({children}: {children: React.ReactNode}) { + const {type} = useContext(Context) + const {textColor} = useToastStyles({type}) + return ( + + {children} + + ) +} + +function useToastStyles({type}: {type: ToastType}) { + const t = useTheme() + return useMemo(() => { + return { + default: { + backgroundColor: select(t.name, { + light: t.atoms.bg_contrast_25.backgroundColor, + dim: t.atoms.bg_contrast_100.backgroundColor, + dark: t.atoms.bg_contrast_100.backgroundColor, + }), + borderColor: select(t.name, { + light: t.atoms.border_contrast_low.borderColor, + dim: t.atoms.border_contrast_high.borderColor, + dark: t.atoms.border_contrast_high.borderColor, + }), + iconColor: select(t.name, { + light: t.atoms.text_contrast_medium.color, + dim: t.atoms.text_contrast_medium.color, + dark: t.atoms.text_contrast_medium.color, + }), + textColor: select(t.name, { + light: t.atoms.text_contrast_medium.color, + dim: t.atoms.text_contrast_medium.color, + dark: t.atoms.text_contrast_medium.color, + }), + }, + success: { + backgroundColor: select(t.name, { + light: t.palette.primary_100, + dim: t.palette.primary_100, + dark: t.palette.primary_50, + }), + borderColor: select(t.name, { + light: t.palette.primary_500, + dim: t.palette.primary_500, + dark: t.palette.primary_500, + }), + iconColor: select(t.name, { + light: t.palette.primary_500, + dim: t.palette.primary_600, + dark: t.palette.primary_600, + }), + textColor: select(t.name, { + light: t.palette.primary_500, + dim: t.palette.primary_600, + dark: t.palette.primary_600, + }), + }, + error: { + backgroundColor: select(t.name, { + light: t.palette.negative_200, + dim: t.palette.negative_25, + dark: t.palette.negative_25, + }), + borderColor: select(t.name, { + light: t.palette.negative_300, + dim: t.palette.negative_300, + dark: t.palette.negative_300, + }), + iconColor: select(t.name, { + light: t.palette.negative_600, + dim: t.palette.negative_600, + dark: t.palette.negative_600, + }), + textColor: select(t.name, { + light: t.palette.negative_600, + dim: t.palette.negative_600, + dark: t.palette.negative_600, + }), + }, + warning: { + backgroundColor: select(t.name, { + light: t.atoms.bg_contrast_25.backgroundColor, + dim: t.atoms.bg_contrast_100.backgroundColor, + dark: t.atoms.bg_contrast_100.backgroundColor, + }), + borderColor: select(t.name, { + light: t.atoms.border_contrast_low.borderColor, + dim: t.atoms.border_contrast_high.borderColor, + dark: t.atoms.border_contrast_high.borderColor, + }), + iconColor: select(t.name, { + light: t.atoms.text_contrast_medium.color, + dim: t.atoms.text_contrast_medium.color, + dark: t.atoms.text_contrast_medium.color, + }), + textColor: select(t.name, { + light: t.atoms.text_contrast_medium.color, + dim: t.atoms.text_contrast_medium.color, + dark: t.atoms.text_contrast_medium.color, + }), + }, + info: { + backgroundColor: select(t.name, { + light: t.atoms.bg_contrast_25.backgroundColor, + dim: t.atoms.bg_contrast_100.backgroundColor, + dark: t.atoms.bg_contrast_100.backgroundColor, + }), + borderColor: select(t.name, { + light: t.atoms.border_contrast_low.borderColor, + dim: t.atoms.border_contrast_high.borderColor, + dark: t.atoms.border_contrast_high.borderColor, + }), + iconColor: select(t.name, { + light: t.atoms.text_contrast_medium.color, + dim: t.atoms.text_contrast_medium.color, + dark: t.atoms.text_contrast_medium.color, + }), + textColor: select(t.name, { + light: t.atoms.text_contrast_medium.color, + dim: t.atoms.text_contrast_medium.color, + dark: t.atoms.text_contrast_medium.color, + }), + }, + }[type] + }, [t, type]) +} diff --git a/src/components/Toast/const.ts b/src/components/Toast/const.ts new file mode 100644 index 0000000000..034d0a2fce --- /dev/null +++ b/src/components/Toast/const.ts @@ -0,0 +1 @@ +export const DEFAULT_TOAST_DURATION = 3000 diff --git a/src/components/Toast/index.e2e.tsx b/src/components/Toast/index.e2e.tsx new file mode 100644 index 0000000000..57daf5bf0d --- /dev/null +++ b/src/components/Toast/index.e2e.tsx @@ -0,0 +1,5 @@ +export function ToastContainer() { + return null +} + +export function show() {} diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx new file mode 100644 index 0000000000..131a796b3f --- /dev/null +++ b/src/components/Toast/index.tsx @@ -0,0 +1,197 @@ +import {useEffect, useMemo, useRef, useState} from 'react' +import {AccessibilityInfo} from 'react-native' +import { + Gesture, + GestureDetector, + GestureHandlerRootView, +} from 'react-native-gesture-handler' +import Animated, { + Easing, + runOnJS, + SlideInUp, + SlideOutUp, + useAnimatedReaction, + useAnimatedStyle, + useSharedValue, + withDecay, + withSpring, +} from 'react-native-reanimated' +import RootSiblings from 'react-native-root-siblings' +import {useSafeAreaInsets} from 'react-native-safe-area-context' + +import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import {atoms as a} from '#/alf' +import {DEFAULT_TOAST_DURATION} from '#/components/Toast/const' +import {Toast} from '#/components/Toast/Toast' +import {type ToastApi, type ToastType} from '#/components/Toast/types' + +const TOAST_ANIMATION_DURATION = 300 + +export function ToastContainer() { + return null +} + +export const toast: ToastApi = { + show(props) { + if (process.env.NODE_ENV === 'test') { + return + } + + AccessibilityInfo.announceForAccessibility(props.a11yLabel) + + const item = new RootSiblings( + ( + item.destroy()} + /> + ), + ) + }, +} + +function AnimatedToast({ + type, + content, + a11yLabel, + duration, + destroy, +}: { + type: ToastType + content: React.ReactNode + a11yLabel: string + duration: number + destroy: () => void +}) { + const {top} = useSafeAreaInsets() + const isPanning = useSharedValue(false) + const dismissSwipeTranslateY = useSharedValue(0) + const [cardHeight, setCardHeight] = useState(0) + + // for the exit animation to work on iOS the animated component + // must not be the root component + // so we need to wrap it in a view and unmount the toast ahead of time + const [alive, setAlive] = useState(true) + + const hideAndDestroyImmediately = () => { + setAlive(false) + setTimeout(() => { + destroy() + }, 1e3) + } + + const destroyTimeoutRef = useRef>() + const hideAndDestroyAfterTimeout = useNonReactiveCallback(() => { + clearTimeout(destroyTimeoutRef.current) + destroyTimeoutRef.current = setTimeout(hideAndDestroyImmediately, duration) + }) + const pauseDestroy = useNonReactiveCallback(() => { + clearTimeout(destroyTimeoutRef.current) + }) + + useEffect(() => { + hideAndDestroyAfterTimeout() + }, [hideAndDestroyAfterTimeout]) + + const panGesture = useMemo(() => { + return Gesture.Pan() + .activeOffsetY([-10, 10]) + .failOffsetX([-10, 10]) + .maxPointers(1) + .onStart(() => { + 'worklet' + if (!alive) return + isPanning.set(true) + runOnJS(pauseDestroy)() + }) + .onUpdate(e => { + 'worklet' + if (!alive) return + dismissSwipeTranslateY.value = e.translationY + }) + .onEnd(e => { + 'worklet' + if (!alive) return + runOnJS(hideAndDestroyAfterTimeout)() + isPanning.set(false) + if (e.velocityY < -100) { + if (dismissSwipeTranslateY.value === 0) { + // HACK: If the initial value is 0, withDecay() animation doesn't start. + // This is a bug in Reanimated, but for now we'll work around it like this. + dismissSwipeTranslateY.value = 1 + } + dismissSwipeTranslateY.value = withDecay({ + velocity: e.velocityY, + velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), + deceleration: 1, + }) + } else { + dismissSwipeTranslateY.value = withSpring(0, { + stiffness: 500, + damping: 50, + }) + } + }) + }, [ + dismissSwipeTranslateY, + isPanning, + alive, + hideAndDestroyAfterTimeout, + pauseDestroy, + ]) + + const topOffset = top + 10 + + useAnimatedReaction( + () => + !isPanning.get() && + dismissSwipeTranslateY.get() < -topOffset - cardHeight, + (isSwipedAway, prevIsSwipedAway) => { + 'worklet' + if (isSwipedAway && !prevIsSwipedAway) { + runOnJS(destroy)() + } + }, + ) + + const animatedStyle = useAnimatedStyle(() => { + const translation = dismissSwipeTranslateY.get() + return { + transform: [ + { + translateY: translation > 0 ? translation ** 0.7 : translation, + }, + ], + } + }) + + return ( + + {alive && ( + setCardHeight(evt.nativeEvent.layout.height)} + accessibilityRole="alert" + accessible={true} + accessibilityLabel={a11yLabel} + accessibilityHint="" + onAccessibilityEscape={hideAndDestroyImmediately} + style={[a.flex_1, animatedStyle]}> + + + + + )} + + ) +} diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx new file mode 100644 index 0000000000..f6ceda568e --- /dev/null +++ b/src/components/Toast/index.web.tsx @@ -0,0 +1,107 @@ +/* + * Note: relies on styles in #/styles.css + */ + +import {useEffect, useState} from 'react' +import {AccessibilityInfo, Pressable, View} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useBreakpoints} from '#/alf' +import {DEFAULT_TOAST_DURATION} from '#/components/Toast/const' +import {Toast} from '#/components/Toast/Toast' +import {type ToastApi, type ToastType} from '#/components/Toast/types' + +const TOAST_ANIMATION_STYLES = { + entering: { + animation: 'toastFadeIn 0.3s ease-out forwards', + }, + exiting: { + animation: 'toastFadeOut 0.2s ease-in forwards', + }, +} + +interface ActiveToast { + type: ToastType + content: React.ReactNode + a11yLabel: string +} +type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void +let globalSetActiveToast: GlobalSetActiveToast | undefined +let toastTimeout: NodeJS.Timeout | undefined +type ToastContainerProps = {} + +export const ToastContainer: React.FC = ({}) => { + const {_} = useLingui() + const {gtPhone} = useBreakpoints() + const [activeToast, setActiveToast] = useState() + const [isExiting, setIsExiting] = useState(false) + + useEffect(() => { + globalSetActiveToast = (t: ActiveToast | undefined) => { + if (!t && activeToast) { + setIsExiting(true) + setTimeout(() => { + setActiveToast(t) + setIsExiting(false) + }, 200) + } else { + if (t) { + AccessibilityInfo.announceForAccessibility(t.a11yLabel) + } + setActiveToast(t) + setIsExiting(false) + } + } + }, [activeToast]) + + return ( + <> + {activeToast && ( + + + setActiveToast(undefined)} + /> + + )} + + ) +} + +export const toast: ToastApi = { + show(props) { + if (toastTimeout) { + clearTimeout(toastTimeout) + } + + globalSetActiveToast?.({ + type: props.type, + content: props.content, + a11yLabel: props.a11yLabel, + }) + + toastTimeout = setTimeout(() => { + globalSetActiveToast?.(undefined) + }, props.duration || DEFAULT_TOAST_DURATION) + }, +} diff --git a/src/components/Toast/types.ts b/src/components/Toast/types.ts new file mode 100644 index 0000000000..9f1245fa22 --- /dev/null +++ b/src/components/Toast/types.ts @@ -0,0 +1,24 @@ +export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' + +export type ToastApi = { + show: (props: { + /** + * The type of toast to show. This determines the styling and icon used. + */ + type: ToastType + /** + * A string, `Text`, or `Span` components to render inside the toast. This + * allows additional formatting of the content, but should not be used for + * interactive elements link links or buttons. + */ + content: React.ReactNode | string + /** + * Accessibility label for the toast, used for screen readers. + */ + a11yLabel: string + /** + * Defaults to `DEFAULT_TOAST_DURATION` from `#components/Toast/const`. + */ + duration?: number + }) => void +} diff --git a/src/style.css b/src/style.css index 35ffe0d3a2..4c5677fbf3 100644 --- a/src/style.css +++ b/src/style.css @@ -369,3 +369,23 @@ input[type='range'][orient='vertical']::-moz-range-thumb { transform: translateY(0); } } + +/* + * #/components/Toast/index.web.tsx + */ +@keyframes toastFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes toastFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} diff --git a/src/view/com/util/Toast.e2e.tsx b/src/view/com/util/Toast.e2e.tsx deleted file mode 100644 index c5582ff0a8..0000000000 --- a/src/view/com/util/Toast.e2e.tsx +++ /dev/null @@ -1 +0,0 @@ -export function show() {} diff --git a/src/view/com/util/Toast.style.tsx b/src/view/com/util/Toast.style.tsx deleted file mode 100644 index 3869e6890b..0000000000 --- a/src/view/com/util/Toast.style.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import {select, type Theme} from '#/alf' -import {Check_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check' -import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' -import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' -import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' - -export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' - -export type LegacyToastType = - | 'xmark' - | 'exclamation-circle' - | 'check' - | 'clipboard-check' - | 'circle-exclamation' - -export const convertLegacyToastType = ( - type: ToastType | LegacyToastType, -): ToastType => { - switch (type) { - // these ones are fine - case 'default': - case 'success': - case 'error': - case 'warning': - case 'info': - return type - // legacy ones need conversion - case 'xmark': - return 'error' - case 'exclamation-circle': - return 'warning' - case 'check': - return 'success' - case 'clipboard-check': - return 'success' - case 'circle-exclamation': - return 'warning' - default: - return 'default' - } -} - -export const TOAST_ANIMATION_CONFIG = { - duration: 300, - damping: 15, - stiffness: 150, - mass: 0.8, - overshootClamping: false, - restSpeedThreshold: 0.01, - restDisplacementThreshold: 0.01, -} - -export const TOAST_TYPE_TO_ICON = { - default: SuccessIcon, - success: SuccessIcon, - error: ErrorIcon, - warning: WarningIcon, - info: CircleInfo, -} - -export const getToastTypeStyles = (t: Theme) => ({ - default: { - backgroundColor: select(t.name, { - light: t.atoms.bg_contrast_25.backgroundColor, - dim: t.atoms.bg_contrast_100.backgroundColor, - dark: t.atoms.bg_contrast_100.backgroundColor, - }), - borderColor: select(t.name, { - light: t.atoms.border_contrast_low.borderColor, - dim: t.atoms.border_contrast_high.borderColor, - dark: t.atoms.border_contrast_high.borderColor, - }), - iconColor: select(t.name, { - light: t.atoms.text_contrast_medium.color, - dim: t.atoms.text_contrast_medium.color, - dark: t.atoms.text_contrast_medium.color, - }), - textColor: select(t.name, { - light: t.atoms.text_contrast_medium.color, - dim: t.atoms.text_contrast_medium.color, - dark: t.atoms.text_contrast_medium.color, - }), - }, - success: { - backgroundColor: select(t.name, { - light: t.palette.primary_100, - dim: t.palette.primary_100, - dark: t.palette.primary_50, - }), - borderColor: select(t.name, { - light: t.palette.primary_500, - dim: t.palette.primary_500, - dark: t.palette.primary_500, - }), - iconColor: select(t.name, { - light: t.palette.primary_500, - dim: t.palette.primary_600, - dark: t.palette.primary_600, - }), - textColor: select(t.name, { - light: t.palette.primary_500, - dim: t.palette.primary_600, - dark: t.palette.primary_600, - }), - }, - error: { - backgroundColor: select(t.name, { - light: t.palette.negative_200, - dim: t.palette.negative_25, - dark: t.palette.negative_25, - }), - borderColor: select(t.name, { - light: t.palette.negative_300, - dim: t.palette.negative_300, - dark: t.palette.negative_300, - }), - iconColor: select(t.name, { - light: t.palette.negative_600, - dim: t.palette.negative_600, - dark: t.palette.negative_600, - }), - textColor: select(t.name, { - light: t.palette.negative_600, - dim: t.palette.negative_600, - dark: t.palette.negative_600, - }), - }, - warning: { - backgroundColor: select(t.name, { - light: t.atoms.bg_contrast_25.backgroundColor, - dim: t.atoms.bg_contrast_100.backgroundColor, - dark: t.atoms.bg_contrast_100.backgroundColor, - }), - borderColor: select(t.name, { - light: t.atoms.border_contrast_low.borderColor, - dim: t.atoms.border_contrast_high.borderColor, - dark: t.atoms.border_contrast_high.borderColor, - }), - iconColor: select(t.name, { - light: t.atoms.text_contrast_medium.color, - dim: t.atoms.text_contrast_medium.color, - dark: t.atoms.text_contrast_medium.color, - }), - textColor: select(t.name, { - light: t.atoms.text_contrast_medium.color, - dim: t.atoms.text_contrast_medium.color, - dark: t.atoms.text_contrast_medium.color, - }), - }, - info: { - backgroundColor: select(t.name, { - light: t.atoms.bg_contrast_25.backgroundColor, - dim: t.atoms.bg_contrast_100.backgroundColor, - dark: t.atoms.bg_contrast_100.backgroundColor, - }), - borderColor: select(t.name, { - light: t.atoms.border_contrast_low.borderColor, - dim: t.atoms.border_contrast_high.borderColor, - dark: t.atoms.border_contrast_high.borderColor, - }), - iconColor: select(t.name, { - light: t.atoms.text_contrast_medium.color, - dim: t.atoms.text_contrast_medium.color, - dark: t.atoms.text_contrast_medium.color, - }), - textColor: select(t.name, { - light: t.atoms.text_contrast_medium.color, - dim: t.atoms.text_contrast_medium.color, - dark: t.atoms.text_contrast_medium.color, - }), - }, -}) - -export const getToastWebAnimationStyles = () => ({ - entering: { - animation: 'toastFadeIn 0.3s ease-out forwards', - }, - exiting: { - animation: 'toastFadeOut 0.2s ease-in forwards', - }, -}) - -export const TOAST_WEB_KEYFRAMES = ` - @keyframes toastFadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - - @keyframes toastFadeOut { - from { - opacity: 1; - } - to { - opacity: 0; - } - } -` diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx index 54ef7042d2..37ec6acb53 100644 --- a/src/view/com/util/Toast.tsx +++ b/src/view/com/util/Toast.tsx @@ -1,234 +1,54 @@ -import {useEffect, useMemo, useRef, useState} from 'react' -import {AccessibilityInfo, View} from 'react-native' -import { - Gesture, - GestureDetector, - GestureHandlerRootView, -} from 'react-native-gesture-handler' -import Animated, { - FadeIn, - FadeOut, - runOnJS, - useAnimatedReaction, - useAnimatedStyle, - useSharedValue, - withDecay, - withSpring, -} from 'react-native-reanimated' -import RootSiblings from 'react-native-root-siblings' -import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {toast} from '#/components/Toast' +import {type ToastType} from '#/components/Toast/types' -import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' -import { - convertLegacyToastType, - getToastTypeStyles, - type LegacyToastType, - TOAST_ANIMATION_CONFIG, - TOAST_TYPE_TO_ICON, - type ToastType, -} from '#/view/com/util/Toast.style' -import {atoms as a, useTheme} from '#/alf' -import {Text} from '#/components/Typography' - -const TIMEOUT = 2e3 - -// Use type overloading to mark certain types as deprecated -sfn -// https://stackoverflow.com/a/78325851/13325987 -export function show(message: string, type?: ToastType): void /** - * @deprecated type is deprecated - use one of `'default' | 'success' | 'error' | 'warning' | 'info'` + * @deprecated use {@link ToastType} and {@link toast} instead + */ +export type LegacyToastType = + | 'xmark' + | 'exclamation-circle' + | 'check' + | 'clipboard-check' + | 'circle-exclamation' + +export const convertLegacyToastType = ( + type: ToastType | LegacyToastType, +): ToastType => { + switch (type) { + // these ones are fine + case 'default': + case 'success': + case 'error': + case 'warning': + case 'info': + return type + // legacy ones need conversion + case 'xmark': + return 'error' + case 'exclamation-circle': + return 'warning' + case 'check': + return 'success' + case 'clipboard-check': + return 'success' + case 'circle-exclamation': + return 'warning' + default: + return 'default' + } +} + +/** + * @deprecated use {@link toast} instead */ -export function show(message: string, type?: LegacyToastType): void export function show( message: string, type: ToastType | LegacyToastType = 'default', ): void { - if (process.env.NODE_ENV === 'test') { - return - } - - AccessibilityInfo.announceForAccessibility(message) - const item = new RootSiblings( - ( - item.destroy()} - /> - ), - ) -} - -function Toast({ - message, - type, - destroy, -}: { - message: string - type: ToastType - destroy: () => void -}) { - const t = useTheme() - const {top} = useSafeAreaInsets() - const isPanning = useSharedValue(false) - const dismissSwipeTranslateY = useSharedValue(0) - const [cardHeight, setCardHeight] = useState(0) - - const toastStyles = getToastTypeStyles(t) - const colors = toastStyles[type] - const IconComponent = TOAST_TYPE_TO_ICON[type] - - // for the exit animation to work on iOS the animated component - // must not be the root component - // so we need to wrap it in a view and unmount the toast ahead of time - const [alive, setAlive] = useState(true) - - const hideAndDestroyImmediately = () => { - setAlive(false) - setTimeout(() => { - destroy() - }, 1e3) - } - - const destroyTimeoutRef = useRef>() - const hideAndDestroyAfterTimeout = useNonReactiveCallback(() => { - clearTimeout(destroyTimeoutRef.current) - destroyTimeoutRef.current = setTimeout(hideAndDestroyImmediately, TIMEOUT) - }) - const pauseDestroy = useNonReactiveCallback(() => { - clearTimeout(destroyTimeoutRef.current) - }) - - useEffect(() => { - hideAndDestroyAfterTimeout() - }, [hideAndDestroyAfterTimeout]) - - const panGesture = useMemo(() => { - return Gesture.Pan() - .activeOffsetY([-10, 10]) - .failOffsetX([-10, 10]) - .maxPointers(1) - .onStart(() => { - 'worklet' - if (!alive) return - isPanning.set(true) - runOnJS(pauseDestroy)() - }) - .onUpdate(e => { - 'worklet' - if (!alive) return - dismissSwipeTranslateY.value = e.translationY - }) - .onEnd(e => { - 'worklet' - if (!alive) return - runOnJS(hideAndDestroyAfterTimeout)() - isPanning.set(false) - if (e.velocityY < -100) { - if (dismissSwipeTranslateY.value === 0) { - // HACK: If the initial value is 0, withDecay() animation doesn't start. - // This is a bug in Reanimated, but for now we'll work around it like this. - dismissSwipeTranslateY.value = 1 - } - dismissSwipeTranslateY.value = withDecay({ - velocity: e.velocityY, - velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), - deceleration: 1, - }) - } else { - dismissSwipeTranslateY.value = withSpring(0, { - stiffness: 500, - damping: 50, - }) - } - }) - }, [ - dismissSwipeTranslateY, - isPanning, - alive, - hideAndDestroyAfterTimeout, - pauseDestroy, - ]) - - const topOffset = top + 10 - - useAnimatedReaction( - () => - !isPanning.get() && - dismissSwipeTranslateY.get() < -topOffset - cardHeight, - (isSwipedAway, prevIsSwipedAway) => { - 'worklet' - if (isSwipedAway && !prevIsSwipedAway) { - runOnJS(destroy)() - } - }, - ) - - const animatedStyle = useAnimatedStyle(() => { - const translation = dismissSwipeTranslateY.get() - return { - transform: [ - { - translateY: translation > 0 ? translation ** 0.7 : translation, - }, - ], - } - }) - - return ( - - {alive && ( - setCardHeight(evt.nativeEvent.layout.height)} - accessibilityRole="alert" - accessible={true} - accessibilityLabel={message} - accessibilityHint="" - onAccessibilityEscape={hideAndDestroyImmediately} - style={[ - a.flex_1, - {backgroundColor: colors.backgroundColor}, - a.shadow_sm, - {borderColor: colors.borderColor, borderWidth: 1}, - a.rounded_sm, - animatedStyle, - ]}> - - - - - - - - {message} - - - - - - )} - - ) + const convertedType = convertLegacyToastType(type) + toast.show({ + type: convertedType, + content: message, + a11yLabel: message, + }) } diff --git a/src/view/com/util/Toast.web.tsx b/src/view/com/util/Toast.web.tsx deleted file mode 100644 index 6b99b30bf3..0000000000 --- a/src/view/com/util/Toast.web.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Note: the dataSet properties are used to leverage custom CSS in public/index.html - */ - -import {useEffect, useState} from 'react' -import {Pressable, StyleSheet, Text, View} from 'react-native' - -import { - convertLegacyToastType, - getToastTypeStyles, - getToastWebAnimationStyles, - type LegacyToastType, - TOAST_TYPE_TO_ICON, - TOAST_WEB_KEYFRAMES, - type ToastType, -} from '#/view/com/util/Toast.style' -import {atoms as a, useTheme} from '#/alf' - -const DURATION = 3500 - -interface ActiveToast { - text: string - type: ToastType -} -type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void - -// globals -// = -let globalSetActiveToast: GlobalSetActiveToast | undefined -let toastTimeout: NodeJS.Timeout | undefined - -// components -// = -type ToastContainerProps = {} -export const ToastContainer: React.FC = ({}) => { - const [activeToast, setActiveToast] = useState() - const [isExiting, setIsExiting] = useState(false) - - useEffect(() => { - globalSetActiveToast = (t: ActiveToast | undefined) => { - if (!t && activeToast) { - setIsExiting(true) - setTimeout(() => { - setActiveToast(t) - setIsExiting(false) - }, 200) - } else { - setActiveToast(t) - setIsExiting(false) - } - } - }, [activeToast]) - - useEffect(() => { - const styleId = 'toast-animations' - if (!document.getElementById(styleId)) { - const style = document.createElement('style') - style.id = styleId - style.textContent = TOAST_WEB_KEYFRAMES - document.head.appendChild(style) - } - }, []) - - const t = useTheme() - - const toastTypeStyles = getToastTypeStyles(t) - const toastStyles = activeToast - ? toastTypeStyles[activeToast.type] - : toastTypeStyles.default - - const IconComponent = activeToast - ? TOAST_TYPE_TO_ICON[activeToast.type] - : TOAST_TYPE_TO_ICON.default - - const animationStyles = getToastWebAnimationStyles() - - return ( - <> - {activeToast && ( - - - - - - {activeToast.text} - - { - setActiveToast(undefined) - }} - /> - - )} - - ) -} - -// methods -// = - -export function show( - text: string, - type: ToastType | LegacyToastType = 'default', -) { - if (toastTimeout) { - clearTimeout(toastTimeout) - } - - globalSetActiveToast?.({text, type: convertLegacyToastType(type)}) - toastTimeout = setTimeout(() => { - globalSetActiveToast?.(undefined) - }, DURATION) -} - -const styles = StyleSheet.create({ - container: { - // @ts-ignore web only - position: 'fixed', - left: 20, - bottom: 20, - // @ts-ignore web only - width: 'calc(100% - 40px)', - maxWidth: 380, - padding: 20, - flexDirection: 'row', - alignItems: 'center', - borderRadius: 10, - borderWidth: 1, - }, - dismissBackdrop: { - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - right: 0, - }, - iconContainer: { - width: 32, - height: 32, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - flexShrink: 0, - }, - icon: { - flexShrink: 0, - }, - text: { - marginLeft: 10, - }, -}) diff --git a/src/view/screens/Storybook/Toasts.tsx b/src/view/screens/Storybook/Toasts.tsx index 4c17f1c332..8fc6f095f7 100644 --- a/src/view/screens/Storybook/Toasts.tsx +++ b/src/view/screens/Storybook/Toasts.tsx @@ -1,65 +1,11 @@ import {Pressable, View} from 'react-native' -import * as Toast from '#/view/com/util/Toast' -import { - getToastTypeStyles, - TOAST_TYPE_TO_ICON, - type ToastType, -} from '#/view/com/util/Toast.style' -import {atoms as a, useTheme} from '#/alf' -import {H1, Text} from '#/components/Typography' - -function ToastPreview({message, type}: {message: string; type: ToastType}) { - const t = useTheme() - const toastStyles = getToastTypeStyles(t) - const colors = toastStyles[type as keyof typeof toastStyles] - const IconComponent = - TOAST_TYPE_TO_ICON[type as keyof typeof TOAST_TYPE_TO_ICON] - - return ( - Toast.show(message, type)} - style={[ - {backgroundColor: colors.backgroundColor}, - a.shadow_sm, - {borderColor: colors.borderColor}, - a.rounded_sm, - a.border, - a.px_sm, - a.py_sm, - a.flex_row, - a.gap_sm, - a.align_center, - ]}> - - - - - - {message} - - - - ) -} +import {show as deprecatedShow} from '#/view/com/util/Toast' +import {atoms as a} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {toast} from '#/components/Toast' +import {Toast} from '#/components/Toast/Toast' +import {H1} from '#/components/Typography' export function Toasts() { return ( @@ -67,35 +13,103 @@ export function Toasts() {

Toast Examples

- - - - - - + toast.show({ + type: 'default', + content: 'Default toast', + a11yLabel: 'Default toast', + }) + }> + + + + toast.show({ + type: 'default', + content: 'Default toast, 6 seconds', + a11yLabel: 'Default toast, 6 seconds', + duration: 6e3, + }) + }> + + + + toast.show({ + type: 'default', + content: + 'This is a longer message to test how the toast handles multiple lines of text content.', + a11yLabel: + 'This is a longer message to test how the toast handles multiple lines of text content.', + }) + }> + - + + + toast.show({ + type: 'success', + content: 'Success toast', + a11yLabel: 'Success toast', + }) + }> + + + + toast.show({ + type: 'info', + content: 'Info toast', + a11yLabel: 'Info toast', + }) + }> + + + + toast.show({ + type: 'warning', + content: 'Warning toast', + a11yLabel: 'Warning toast', + }) + }> + + + + toast.show({ + type: 'error', + content: 'Error toast', + a11yLabel: 'Error toast', + }) + }> + + - - - - - - - - - - - - - - - + ) diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index afcc1c4e73..40ef79cca3 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -91,6 +91,8 @@ function StorybookInner() { + + - - - ))} + + ))} - - - - {['gradient_sky', 'gradient_midnight', 'gradient_sunrise'].map( - name => ( - - - - - ), - )} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx index ad130b376d..45a1d9aa00 100644 --- a/src/view/screens/Storybook/Forms.tsx +++ b/src/view/screens/Storybook/Forms.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {TextInput, View} from 'react-native' +import {type TextInput, View} from 'react-native' import {atoms as a} from '#/alf' import {Button, ButtonText} from '#/components/Button' @@ -216,8 +216,8 @@ export function Forms() {