Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87c786951b | |||
| a892057e29 | |||
| 56e16fcdf1 | |||
| 3f7cf5b592 | |||
| 11631c2f4d | |||
| 674f73b813 | |||
| 6406a2ef4d | |||
| cc26e5a090 | |||
| a329899552 | |||
| 9d363e983b | |||
| c19fec3f48 | |||
| 11c80a1fbd | |||
| e113da50d4 | |||
| 14f8ee10f9 | |||
| 0e04159335 | |||
| 04ca3f9829 | |||
| c328b9b6a6 | |||
| 51381d41b0 | |||
| c4172cef99 | |||
| 929af7d8be | |||
| cc3622b583 | |||
| 45aa1a67b0 | |||
| 7f59d8fcfb | |||
| df3cea1c78 | |||
| e87ec032cd | |||
| 3b7284a86b | |||
| 01486d27e0 | |||
| fd86a7d3d8 | |||
| 4562ca8e38 | |||
| 0ba971b1f6 | |||
| 1f5c698165 | |||
| 1e57ecfdd1 | |||
| 1123b31bf7 | |||
| d76e894220 | |||
| 4bc0013a4d | |||
| 34b6f1d323 | |||
| 221c4d9c5b | |||
| bd920d80e0 | |||
| 2d93273956 | |||
| 70195d7059 | |||
| afb3cdc26f | |||
| bb33eb91cb | |||
| b89167f07c | |||
| 3677c98873 | |||
| 9fe0084874 | |||
| 54da80dfb0 | |||
| 59d2bf09d7 | |||
| dc31e7fd12 | |||
| a4f2811f39 | |||
| 0c93d1e416 | |||
| 75e4180800 | |||
| ca226f589d | |||
| 3568cdc42d | |||
| f6a22c4f05 | |||
| d991118215 | |||
| 34d1c75756 | |||
| f5c166a055 | |||
| 6f696e4b73 | |||
| 4aae6b2c22 | |||
| ea4f70fb1e | |||
| 0e8e0b2ab4 | |||
| 0912119c77 | |||
| 855457c9fa | |||
| b804f267cc | |||
| c52e730112 | |||
| e1ba8fe1b0 | |||
| 837771a311 | |||
| 532681d5c0 | |||
| 97b3978fb8 | |||
| 22b31c52bb | |||
| 2f0d417417 | |||
| da93da4191 | |||
| 419a49019d | |||
| aafcb2eeff | |||
| f5f980e58e | |||
| 3d736c76b7 | |||
| 9b0adcb55f | |||
| bd5e56ce25 | |||
| 34a7bc72a9 | |||
| 4ed34cb242 | |||
| 34274647f2 | |||
| 1f3c619dd2 | |||
| 98fa8eafdf | |||
| e7d82063ae | |||
| b801a24ef2 | |||
| 82db01fc60 | |||
| 9d5e634780 | |||
| 8c54f01b3e | |||
| 17d5dcebf7 | |||
| 87548a6578 | |||
| 2a4378c40b | |||
| 89cb08e93c | |||
| 44a44e86be | |||
| 892dcf65c0 | |||
| 51401e4c0c | |||
| 126ffed4ea | |||
| 98b9f1c21d | |||
| cc3b9ac0d9 | |||
| cab5b69a5e | |||
| a3951c2bd7 | |||
| 7a47ad3a4d | |||
| d727be8c1a | |||
| 1e6725c0a1 | |||
| 1c349ad7da | |||
| 724013df1b | |||
| 1cbe7a39e6 | |||
| 78a5c222bf | |||
| 5cf6d8fa21 | |||
| 1ac5a71d61 | |||
| 61283da526 | |||
| dd7c950cc6 | |||
| b79908ecea | |||
| 8cb6c7babe | |||
| 32ec5330b0 | |||
| 17a8fe87c2 | |||
| 0ccc029716 | |||
| 5db6b4e2fd | |||
| 5494f7deb5 | |||
| ec49988fa7 | |||
| f1cfa37bab | |||
| a1f094534a | |||
| 863fdcd8c1 | |||
| 1cc942a869 | |||
| e99e04a458 | |||
| c2ceb8057f | |||
| d58be1adea | |||
| f99a17bb6e | |||
| ce28129674 | |||
| 87a177bbe8 | |||
| 03f8020ee2 | |||
| 07c34a6548 | |||
| 1c02724ac4 | |||
| 17c1ee4869 | |||
| 157a1893fd | |||
| cc3c01267d | |||
| 973d0b06f2 | |||
| 0be8b1dd93 | |||
| 91517af01c | |||
| 15c6c7955d | |||
| 904097e8c1 | |||
| b4bad1187a | |||
| 8e3b252b86 | |||
| e375ceb5b8 | |||
| 45bdba5dfa | |||
| 196e81eb0c | |||
| a35c4ac4ba | |||
| c6a40088dd | |||
| b57827f23f | |||
| 27e4f84f3f | |||
| 0751420a29 | |||
| 8263175a03 | |||
| 2f36a6de5a | |||
| de348db116 | |||
| 8a6347ad7c | |||
| 82c505f286 | |||
| ffdfb3c0e7 | |||
| aa06d68ca6 | |||
| 3ddb4a419e | |||
| e4ba1b4378 | |||
| a5f0e8839a | |||
| d9eacc3d98 | |||
| bbf7603250 |
@@ -1,13 +1,15 @@
|
||||
version: 2
|
||||
# Dependabot auto-update config.
|
||||
#
|
||||
# Cooldown (7 days) is the point of this config: it delays version-update
|
||||
# PRs until a newly-published version has aged. Supply-chain attacks like
|
||||
# npm and GitHub Actions use a 7-day cooldown so newly published versions age
|
||||
# before a PR opens. Supply-chain attacks like
|
||||
# the tanstack Shai-Hulud compromise (2026-05-11) live minutes-to-hours
|
||||
# before the registry yanks them; a 7-day cooldown keeps poisoned
|
||||
# versions out of our lockfiles.
|
||||
#
|
||||
# Security updates bypass cooldown and continue to flow immediately. See:
|
||||
# Security updates bypass cooldown and continue to flow immediately. Docker
|
||||
# uses a shorter 3-day operational-freshness policy and does not support
|
||||
# Dependabot security updates. See:
|
||||
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#cooldown
|
||||
#
|
||||
# Auto-merge is deliberately NOT enabled. Every dependabot PR gets human
|
||||
@@ -42,3 +44,24 @@ updates:
|
||||
actions:
|
||||
patterns: ["*"]
|
||||
update-types: [minor, patch]
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "09:00"
|
||||
timezone: America/Los_Angeles
|
||||
cooldown:
|
||||
default-days: 3
|
||||
open-pull-requests-limit: 5
|
||||
groups:
|
||||
docker-base-images:
|
||||
group-by: dependency-name
|
||||
patterns: ["*"]
|
||||
ignore:
|
||||
# Node 24 remains the active LTS line. Revisit Node 26 after it enters
|
||||
# LTS in October 2026: https://github.com/bluesky-social/social-app/issues/11480
|
||||
- dependency-name: node
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Asset notices
|
||||
|
||||
# Verifies that every path named in ASSETS.md and NOTICE.md still exists.
|
||||
#
|
||||
# Those files tell forkers which assets our MIT license does not cover, and carry the third-party
|
||||
# attribution notices we are required to pass along. If an asset moves and the notice is not
|
||||
# updated, the notice silently stops meaning anything. This job makes that visible in review
|
||||
# rather than a year later.
|
||||
#
|
||||
# No paths filter: the notices also reference files under src/ (e.g. the inline logo components),
|
||||
# so any rename anywhere in the tree can rot a notice. The check runs in under a second.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check asset licensing notices
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Check out Git repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⚙️ Set up Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: package.json
|
||||
|
||||
- name: 📜 Verify asset notices
|
||||
run: node scripts/check-asset-notices.mjs
|
||||
@@ -22,13 +22,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 🔧 Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: 🔑 Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.USERNAME}}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
name: build-and-push-bskyweb-ghcr
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- Dockerfile
|
||||
- Dockerfile.bskylink
|
||||
- Dockerfile.bskyogcard
|
||||
- Dockerfile.embedr
|
||||
- .github/workflows/build-and-push-bskyweb-ghcr.yaml
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -13,8 +20,40 @@ env:
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
verify-containers:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- image: bskyweb
|
||||
file: Dockerfile
|
||||
- image: bskylink
|
||||
file: Dockerfile.bskylink
|
||||
- image: bskyogcard
|
||||
file: Dockerfile.bskyogcard
|
||||
- image: embedr
|
||||
file: Dockerfile.embedr
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: 🔧 Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
- name: Build ${{ matrix.image }}
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
cache-from: type=gha,scope=${{ matrix.image }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
|
||||
|
||||
bskyweb-container-ghcr:
|
||||
if: github.repository == 'bluesky-social/social-app'
|
||||
if: github.event_name == 'push' && github.repository == 'bluesky-social/social-app'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -23,13 +62,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 🔧 Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: 🔑 Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.USERNAME }}
|
||||
|
||||
@@ -22,13 +22,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 🔧 Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: 🔑 Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.USERNAME}}
|
||||
|
||||
@@ -22,13 +22,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 🔧 Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: 🔑 Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.USERNAME}}
|
||||
|
||||
@@ -22,13 +22,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 🔧 Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: 🔑 Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.USERNAME}}
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
version-code: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 5
|
||||
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
with:
|
||||
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
@@ -132,6 +132,16 @@ jobs:
|
||||
if-no-files-found: error
|
||||
path: build.aab
|
||||
|
||||
- name: 📝 Write build summary
|
||||
env:
|
||||
REMOTE_VERSION_CODE: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
||||
run: |
|
||||
{
|
||||
echo "### Android build number"
|
||||
echo
|
||||
echo "\`$REMOTE_VERSION_CODE\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
submit:
|
||||
name: Submit to Google Play
|
||||
runs-on: ubuntu-latest
|
||||
@@ -141,7 +151,7 @@ jobs:
|
||||
steps:
|
||||
# eas submit reads app config from the repo, so we need a checkout.
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 5
|
||||
|
||||
@@ -162,7 +172,7 @@ jobs:
|
||||
|
||||
- name: 🔔 Notify Slack of Play Store Submission
|
||||
if: ${{ inputs.profile == 'production' }}
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
@@ -185,7 +195,7 @@ jobs:
|
||||
|
||||
# bundletool needs a JRE. ubuntu-latest ships a default JDK, but pin it explicitly
|
||||
# like the build job so the toolchain is deterministic.
|
||||
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
@@ -235,7 +245,7 @@ jobs:
|
||||
path: build.apk
|
||||
|
||||
- name: 🔔 Notify Slack of APK Artifact
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
@@ -298,7 +308,7 @@ jobs:
|
||||
|
||||
- name: 🔔 Notify Slack of Release Attachment
|
||||
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
|
||||
@@ -18,6 +18,11 @@ on:
|
||||
- QA Team
|
||||
- Software Mansion
|
||||
default: none
|
||||
changelog:
|
||||
type: string
|
||||
description: TestFlight "What to Test" notes (only applied when a group is selected)
|
||||
required: false
|
||||
default: ''
|
||||
workflow_call:
|
||||
inputs:
|
||||
profile:
|
||||
@@ -81,7 +86,7 @@ jobs:
|
||||
build-number: ${{ steps.ipa-build-number.outputs.build-number }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 5
|
||||
|
||||
@@ -211,6 +216,16 @@ jobs:
|
||||
${{ env.BUILD_DIR }}/Bluesky.ipa
|
||||
${{ env.BUILD_DIR }}/Bluesky.app.dSYM.zip
|
||||
|
||||
- name: 📝 Write build summary
|
||||
env:
|
||||
REMOTE_BUILD_NUMBER: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
|
||||
run: |
|
||||
{
|
||||
echo "### iOS build number"
|
||||
echo
|
||||
echo "\`$REMOTE_BUILD_NUMBER\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
submit:
|
||||
name: Submit iOS
|
||||
# Submission and dSYM upload are I/O bound and don't need the xlarge builder.
|
||||
@@ -218,7 +233,7 @@ jobs:
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
# eas submit reads the app config from the repo
|
||||
fetch-depth: 5
|
||||
@@ -246,7 +261,7 @@ jobs:
|
||||
|
||||
- name: 🔔 Notify Slack of Production Build
|
||||
if: ${{ inputs.profile == 'production' }}
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
@@ -270,6 +285,7 @@ jobs:
|
||||
- name: 🧪 Assign build to TestFlight group
|
||||
env:
|
||||
TESTFLIGHT_GROUP: ${{ inputs.testFlightGroup }}
|
||||
CHANGELOG: ${{ inputs.changelog }}
|
||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
|
||||
@@ -288,6 +304,12 @@ jobs:
|
||||
--arg key "$key_content" \
|
||||
'{key_id: $key_id, issuer_id: $issuer_id, key: $key, in_house: false}' \
|
||||
> asc_api_key.json
|
||||
# changelog is optional, and passing it empty would blank out whatever "What to
|
||||
# Test" notes the build already has, so only include the flag when it is set.
|
||||
changelog_arg=()
|
||||
if [ -n "$CHANGELOG" ]; then
|
||||
changelog_arg=(changelog:"$CHANGELOG")
|
||||
fi
|
||||
# app_platform is required in non-interactive mode: distribute_only otherwise
|
||||
# calls fetch_app_platform, which prompts for input and crashes without a TTY.
|
||||
fastlane run upload_to_testflight \
|
||||
@@ -298,4 +320,5 @@ jobs:
|
||||
app_version:"$APP_VERSION" \
|
||||
build_number:"$BUILD_NUMBER" \
|
||||
groups:"$TESTFLIGHT_GROUP" \
|
||||
notify_external_testers:true
|
||||
notify_external_testers:true \
|
||||
"${changelog_arg[@]}"
|
||||
|
||||
@@ -17,6 +17,14 @@ on:
|
||||
type: string
|
||||
description: Runtime version (in x.x.x format) that this update is for
|
||||
required: true
|
||||
iosBuildNumber:
|
||||
type: string
|
||||
description: iOS build number of the native build this update targets
|
||||
(required for production)
|
||||
androidVersionCode:
|
||||
type: string
|
||||
description: Android version code of the native build this update
|
||||
targets (required for production)
|
||||
|
||||
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
|
||||
permissions:
|
||||
@@ -61,12 +69,25 @@ jobs:
|
||||
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
|
||||
if: ${{ inputs.runtimeVersion }}
|
||||
run: |
|
||||
if [ -z "$RUNTIME_VERSION" ]; then
|
||||
[[ "$RUNTIME_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1
|
||||
fi
|
||||
[[ "$RUNTIME_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1
|
||||
|
||||
# Production OTAs are bound to the specific native build they target, so
|
||||
# the build numbers must be entered manually rather than read from the
|
||||
# global EAS counters, which advance with every testflight build and so
|
||||
# point past older production releases
|
||||
- name: 🧐 Validate build numbers
|
||||
if: ${{ inputs.channel == 'production' }}
|
||||
env:
|
||||
IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }}
|
||||
ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }}
|
||||
run: |
|
||||
[[ "$IOS_BUILD_NUMBER" =~ ^[0-9]+$ ]] ||
|
||||
(echo "A numeric iosBuildNumber is required for production updates" && exit 1)
|
||||
[[ "$ANDROID_VERSION_CODE" =~ ^[0-9]+$ ]] ||
|
||||
(echo "A numeric androidVersionCode is required for production updates" && exit 1)
|
||||
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -95,7 +116,7 @@ jobs:
|
||||
echo "version-changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
@@ -232,7 +253,7 @@ jobs:
|
||||
- name: ☁️ Configure AWS credentials (denis)
|
||||
if: ${{ !steps.fingerprint.outputs.includes-changes &&
|
||||
!steps.version.outputs.version-changed }}
|
||||
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
||||
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::007404326489:role/denis-ci-publish
|
||||
aws-region: us-east-2
|
||||
@@ -253,6 +274,10 @@ jobs:
|
||||
env:
|
||||
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
|
||||
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
|
||||
# When set (required for production), these take precedence over the
|
||||
# global EAS counters inside the use-build-number wrapper
|
||||
BSKY_IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }}
|
||||
BSKY_ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }}
|
||||
|
||||
- name: 📦 Package Bundle and 🚀 Deploy (legacy ota1)
|
||||
if: ${{ !steps.fingerprint.outputs.includes-changes &&
|
||||
@@ -262,6 +287,10 @@ jobs:
|
||||
DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }}
|
||||
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
|
||||
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
|
||||
# When set (required for production), these take precedence over the
|
||||
# global EAS counters inside the use-build-number wrapper
|
||||
BSKY_IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }}
|
||||
BSKY_ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }}
|
||||
|
||||
buildIfNecessaryIOS:
|
||||
name: Build and Submit iOS
|
||||
|
||||
@@ -26,6 +26,7 @@ permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
actions: read
|
||||
# Required for claude-code-action's GitHub App token exchange.
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
@@ -54,23 +55,21 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: ☁️ Configure AWS credentials (OIDC)
|
||||
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
|
||||
aws-region: us-east-2
|
||||
|
||||
- name: 🤖 Claude
|
||||
uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171
|
||||
uses: anthropics/claude-code-action@5ef2e550a465a721f4f45e4a7d3c340c873e1dcc # v1.0.190
|
||||
env:
|
||||
ANTHROPIC_BASE_URL: https://agentgateway.k1.prod.bsky.dev
|
||||
with:
|
||||
use_bedrock: 'true'
|
||||
# Agent Gateway service keys use Authorization: Bearer, the wire
|
||||
# shape emitted by the action's OAuth-token input.
|
||||
claude_code_oauth_token: ${{ secrets.AGENT_GATEWAY_CLAUDE_GH_REVIEW_KEY }}
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
track_progress: true
|
||||
claude_args: |
|
||||
--model global.anthropic.claude-opus-4-8
|
||||
--model claude-opus-4-8:api
|
||||
--allowedTools "mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: claude-review
|
||||
|
||||
# Automatic Claude review on PR creation/update, via Bedrock (OIDC, no
|
||||
# long-lived tokens). Self-contained: this intentionally uses upstream
|
||||
# Automatic Claude review on PR creation/update, via Agent Gateway.
|
||||
# Self-contained: this intentionally uses upstream
|
||||
# claude-code-action defaults rather than the org reusable workflows in
|
||||
# bluesky-social/.github (which a public repo cannot call, and whose
|
||||
# customizations added no value over upstream).
|
||||
@@ -16,14 +16,14 @@ permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
# Required for claude-code-action's GitHub App token exchange.
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
review:
|
||||
# Internal PRs only. This repo is public: fork PRs are the bulk of
|
||||
# community traffic and MUST NOT trigger reviews (no Bedrock spend on
|
||||
# unvetted code, and fork PRs can't mint the OIDC token anyway —
|
||||
# belt-and-braces with this explicit guard). Branch PRs can only be
|
||||
# community traffic and MUST NOT trigger reviews (no gateway spend on
|
||||
# unvetted code). Branch PRs can only be
|
||||
# created by people with write access, i.e. org members.
|
||||
# Bot-authored PRs (dependabot, changesets) are also skipped.
|
||||
if: >
|
||||
@@ -40,25 +40,23 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: ☁️ Configure AWS credentials (OIDC)
|
||||
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
|
||||
aws-region: us-east-2
|
||||
|
||||
- name: 🤖 Claude review
|
||||
uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171
|
||||
uses: anthropics/claude-code-action@5ef2e550a465a721f4f45e4a7d3c340c873e1dcc # v1.0.190
|
||||
env:
|
||||
ANTHROPIC_BASE_URL: https://agentgateway.k1.prod.bsky.dev
|
||||
with:
|
||||
use_bedrock: 'true'
|
||||
# Agent Gateway service keys use Authorization: Bearer, the wire
|
||||
# shape emitted by the action's OAuth-token input.
|
||||
claude_code_oauth_token: ${{ secrets.AGENT_GATEWAY_CLAUDE_GH_REVIEW_KEY }}
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
track_progress: true
|
||||
claude_args: |
|
||||
--model global.anthropic.claude-opus-4-8
|
||||
--model claude-opus-4-8:api
|
||||
--allowedTools "mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
|
||||
prompt: |
|
||||
REPO: ${{ github.repository }}
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Git Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: 🔧 Set up Go tooling
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: bskyweb/go.mod
|
||||
cache-dependency-path: bskyweb/go.sum
|
||||
@@ -36,9 +36,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Git Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: 🔧 Set up Go tooling
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: bskyweb/go.mod
|
||||
cache-dependency-path: bskyweb/go.sum
|
||||
|
||||
@@ -22,10 +22,17 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
[lint, prettier, 'typecheck:ios', 'typecheck:android', 'typecheck:web']
|
||||
[
|
||||
lint,
|
||||
prettier,
|
||||
'lexicons:verify',
|
||||
'typecheck:ios',
|
||||
'typecheck:android',
|
||||
'typecheck:web',
|
||||
]
|
||||
steps:
|
||||
- name: ⬇️ Check out Git repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: 🔍 Verify Node version pins match package.json
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -51,7 +58,7 @@ jobs:
|
||||
v=$(grep -oE '"node":[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' eas.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)
|
||||
check "eas.json" "$v"
|
||||
exit $rc
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- name: 🔧 Install node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
@@ -88,8 +95,8 @@ jobs:
|
||||
shard: [1, 2, 3, 4]
|
||||
steps:
|
||||
- name: ⬇️ Check out Git repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- name: 🔧 Install node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
notes: ${{ steps.notes.outputs.notes }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 🔔 Notify Slack
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 🔔 Notify Slack
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: ☕️ Set up Java 17
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: ☕️ Set up Java 17
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
@@ -361,7 +361,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -417,7 +417,7 @@ jobs:
|
||||
|
||||
- name: 🔔 Notify Slack of E2E failures
|
||||
if: steps.summary.outputs.notify == 'true'
|
||||
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
||||
with:
|
||||
webhook: ${{ secrets.E2E_FAILURES_SLACK_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Check out Git repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}}
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- name: 🔧 Install node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
commit_message: Nightly source-language update
|
||||
file_pattern: ./src/locale/locales/en/messages.po
|
||||
- name: 🚀 Push source lang to Crowdin
|
||||
uses: crowdin/github-action@52aa776766211d83d975df51f3b9c53c2f8ba35f # v2.16.3
|
||||
uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2.17.0
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_sources_args: "-b main"
|
||||
|
||||
@@ -22,6 +22,7 @@ permissions: {}
|
||||
# job downloads. Bump this one line to roll denis.
|
||||
env:
|
||||
DENIS_RELEASE_TAG: denis-v0.1.1
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
jobs:
|
||||
# Populate this from main so every PR can restore the same trusted baseline.
|
||||
@@ -32,9 +33,9 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
@@ -71,11 +72,11 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
@@ -166,7 +167,7 @@ jobs:
|
||||
includes-changes: ${{ steps.fingerprint.outputs.includes-changes }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 100
|
||||
|
||||
@@ -174,7 +175,7 @@ jobs:
|
||||
run: git fetch origin main:main --depth 100
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
@@ -185,7 +186,7 @@ jobs:
|
||||
- name: 📷 Check fingerprint and install dependencies
|
||||
id: fingerprint
|
||||
timeout-minutes: 5
|
||||
uses: bluesky-social/github-actions/fingerprint-native@b5556913e4aef3964cfd5936d0add3fc0d809bdb # v0.2.0
|
||||
uses: bluesky-social/github-actions/fingerprint-native@abc6a46eb4badf243f55bfd7d6cec42722456300 # v0.3.0
|
||||
with:
|
||||
profile: pull-request
|
||||
|
||||
@@ -195,14 +196,7 @@ jobs:
|
||||
with:
|
||||
header: fingerprint-diff
|
||||
message: |
|
||||
The Pull Request introduced fingerprint changes against the base commit:
|
||||
<details><summary>Fingerprint diff</summary>
|
||||
|
||||
```json
|
||||
${{ steps.fingerprint.outputs.diff }}
|
||||
```
|
||||
|
||||
</details>
|
||||
The Pull Request introduced native fingerprint changes against the base commit.
|
||||
|
||||
---
|
||||
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
|
||||
@@ -275,9 +269,13 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
outputs:
|
||||
release-version: ${{ steps.env.outputs.release-version }}
|
||||
ios-build-number: ${{ steps.build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
|
||||
android-build-number: ${{ steps.build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
@@ -308,7 +306,7 @@ jobs:
|
||||
pnpm export
|
||||
|
||||
- name: ☁️ Configure AWS credentials (denis, PR-scoped)
|
||||
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
||||
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::007404326489:role/denis-ci-publish-pr
|
||||
aws-region: us-east-2
|
||||
@@ -342,11 +340,18 @@ jobs:
|
||||
app-id: ${{ vars.SYNC_INTERNAL_APP_ID }}
|
||||
private-key: ${{ secrets.SYNC_INTERNAL_PK }}
|
||||
|
||||
- name: 🔢 Get native build numbers
|
||||
id: build-info
|
||||
run: bash scripts/setGitHubOutput.sh
|
||||
|
||||
- name: 🚀 Publish OTA to denis (S3)
|
||||
run: pnpm use-build-number bash scripts/denisPublish.sh
|
||||
env:
|
||||
RUNTIME_VERSION: ''
|
||||
CHANNEL_NAME: pull-request-${{ github.event.pull_request.number }}
|
||||
# Pin the publish to the same values exposed in the install link.
|
||||
BSKY_IOS_BUILD_NUMBER: ${{ steps.build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
|
||||
BSKY_ANDROID_VERSION_CODE: ${{ steps.build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
||||
|
||||
comment-pr-ota:
|
||||
name: Comment PR OTA install link
|
||||
@@ -362,6 +367,6 @@ jobs:
|
||||
message: |
|
||||
The OTA deployment for this PR was successful! You may now apply it by either scanning the QR code or opening the deep link below in your browser:
|
||||
|
||||
<img src="https://bsky-qr.vercel.app?channel=pull-request-${{ github.event.pull_request.number }}" width="300" height="300" alt="QR code for the PR OTA deployment">
|
||||
<img src="https://bsky-qr.vercel.app?channel=pull-request-${{ github.event.pull_request.number }}&releaseVersion=${{ needs.publish-pr-ota.outputs.release-version }}&iosBuildNumber=${{ needs.publish-pr-ota.outputs.ios-build-number }}&androidBuildNumber=${{ needs.publish-pr-ota.outputs.android-build-number }}" width="300" height="300" alt="QR code for the PR OTA deployment">
|
||||
|
||||
`bluesky://intent/apply-ota?channel=pull-request-${{ github.event.pull_request.number }}`
|
||||
`bluesky://intent/apply-ota?channel=pull-request-${{ github.event.pull_request.number }}&releaseVersion=${{ needs.publish-pr-ota.outputs.release-version }}&iosBuildNumber=${{ needs.publish-pr-ota.outputs.ios-build-number }}&androidBuildNumber=${{ needs.publish-pr-ota.outputs.android-build-number }}`
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
if: github.repository == 'bluesky-social/social-app'
|
||||
steps:
|
||||
- name: ⬇️ Checkout public repo
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Don't persist the checkout auth header; the push below authenticates
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Check out PR HEAD
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: git fetch origin $BASE_REF --depth=1
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
|
||||
- name: 🔧 Install node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
|
||||
@@ -21,12 +21,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Check out Git repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 🛡️ Run zizmor
|
||||
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
|
||||
with:
|
||||
# Annotate the PR directly instead of uploading SARIF to the
|
||||
# security tab, and fail the check on any finding
|
||||
|
||||
@@ -117,6 +117,9 @@ src/locale/locales/**/messages.js
|
||||
src/locale/locales/**/messages.mjs
|
||||
src/locale/locales/**/messages.ts
|
||||
|
||||
# generated lexicon schemas (pnpm lexicons:generate)
|
||||
src/lexicons/
|
||||
|
||||
# local builds
|
||||
*.apk
|
||||
*.aab
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Codemod to replace BskyAgent with AtpAgent
|
||||
*
|
||||
* Before:
|
||||
* import {BskyAgent} from '@atproto/api`
|
||||
* BskyAgent.appLabelers.includes(labeler)
|
||||
*
|
||||
* After:
|
||||
* import {AtpAgent} from '@atproto/api`
|
||||
* AtpAgent.appLabelers.includes(labeler)
|
||||
*
|
||||
* Handles import specifiers, type annotations, static member access
|
||||
* (BskyAgent.configure), `extends BskyAgent`, and `new BskyAgent()`. Whole
|
||||
* identifiers only, so names like `OpaqueBskyAgent` are left untouched.
|
||||
*
|
||||
* Usage: jscodeshift -t .jscodeshift/repo/bsky-agent.js <file-path>
|
||||
* Example: jscodeshift -t .jscodeshift/repo/bsky-agent.js src/lib/moderation.ts
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
export const parser = 'tsx'
|
||||
|
||||
export default function transformer(file, api) {
|
||||
const j = api.jscodeshift
|
||||
const root = j(file.source)
|
||||
|
||||
// Replace every standalone `BskyAgent` identifier with `AtpAgent`. This
|
||||
// covers imports, type references, member expressions, `extends`, and `new`.
|
||||
root
|
||||
.find(j.Identifier, {name: 'BskyAgent'})
|
||||
.replaceWith(() => j.identifier('AtpAgent'))
|
||||
|
||||
// Renaming can leave a duplicate `AtpAgent` specifier on the @atproto/api
|
||||
// import if the file already imported it. Dedupe by imported name, keeping
|
||||
// the type-only modifier only if every duplicate was type-only.
|
||||
root
|
||||
.find(j.ImportDeclaration, {source: {value: '@atproto/api'}})
|
||||
.forEach(path => {
|
||||
const seen = new Map()
|
||||
for (const spec of path.value.specifiers) {
|
||||
if (spec.type !== 'ImportSpecifier') {
|
||||
seen.set(Symbol(), spec)
|
||||
continue
|
||||
}
|
||||
const name = spec.imported.name
|
||||
const existing = seen.get(name)
|
||||
if (!existing) {
|
||||
seen.set(name, spec)
|
||||
} else if (
|
||||
existing.importKind === 'type' &&
|
||||
spec.importKind !== 'type'
|
||||
) {
|
||||
// Prefer the value (non-type) import if either usage needs it.
|
||||
seen.set(name, spec)
|
||||
}
|
||||
}
|
||||
path.value.specifiers = Array.from(seen.values())
|
||||
})
|
||||
|
||||
return root.toSource()
|
||||
}
|
||||
@@ -60,8 +60,10 @@
|
||||
"bskyweb/**",
|
||||
"bskyembed/**",
|
||||
"bskyogcard/**",
|
||||
"lint-rules/**",
|
||||
"src/locale/locales/_build/**",
|
||||
"src/locale/locales/**/*.js",
|
||||
"src/lexicons/**",
|
||||
"*.e2e.ts",
|
||||
"*.e2e.tsx",
|
||||
"eslint.config.mjs",
|
||||
@@ -238,7 +240,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"bsky-internal/use-exact-imports": "error",
|
||||
"bsky-internal/use-prefixed-imports": "error",
|
||||
"bsky-internal/lingui-msg-rule": "error",
|
||||
"react/display-name": "error",
|
||||
@@ -259,7 +260,6 @@
|
||||
"react/no-unsafe": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/hook-use-state": "warn",
|
||||
"react-native/no-inline-styles": "off",
|
||||
"react-native-a11y/has-accessibility-hint": "error",
|
||||
"react-native-a11y/has-accessibility-props": "error",
|
||||
"react-native-a11y/has-valid-accessibility-actions": "error",
|
||||
@@ -274,7 +274,7 @@
|
||||
"react-native-a11y/has-valid-accessibility-ignores-invert-colors": "error",
|
||||
"react-native-a11y/has-valid-accessibility-live-region": "error",
|
||||
"react-native-a11y/has-valid-important-for-accessibility": "error",
|
||||
"react-compiler/react-compiler": "warn",
|
||||
"react/react-compiler": "warn",
|
||||
"simple-import-sort/imports": [
|
||||
"error",
|
||||
{
|
||||
@@ -374,9 +374,7 @@
|
||||
},
|
||||
"jsPlugins": [
|
||||
"eslint-plugin-bsky-internal",
|
||||
"eslint-plugin-react-native",
|
||||
"eslint-plugin-react-native-a11y",
|
||||
"eslint-plugin-react-compiler",
|
||||
"eslint-plugin-simple-import-sort"
|
||||
],
|
||||
"env": {
|
||||
@@ -387,6 +385,7 @@
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"bskylink/**/*.{js,jsx,ts,tsx}",
|
||||
"bskyogcard/**/*.{js,jsx,ts,tsx}",
|
||||
"dev-env/**/*.{js,jsx,ts,tsx}"
|
||||
],
|
||||
@@ -404,4 +403,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
android
|
||||
ios
|
||||
src/locale/locales
|
||||
src/lexicons
|
||||
lib/react-compiler-runtime
|
||||
bskyweb/static
|
||||
coverage
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# Asset licensing
|
||||
|
||||
The [MIT license](./LICENSE) in this repository covers our source code. It does not cover every file in the tree.
|
||||
|
||||
Some of the images, icons, fonts, and brand assets here are licensed to Bluesky Social PBC by third parties, or are our trademarks, or are third-party trademarks. We cannot pass those rights on to you. This document identifies them and names who holds them.
|
||||
|
||||
## This is not a license change
|
||||
|
||||
The MIT license on our source code is unchanged. This document records rights that Bluesky never held, and therefore could never have granted you.
|
||||
|
||||
We updated this file in August 2026 so the repository no longer carries a blanket MIT license with no asset carve-out and forking guidelines that ignored commissioned artwork.
|
||||
|
||||
For the assets Bluesky itself owns, we are not treating anyone's past use as bad faith. For the rest we are not the rights holder. The tables below name them. If you have shipped one of these in a fork, the [If you are forking](#if-you-are-forking) checklist is the shortest path to a clean position.
|
||||
|
||||
## Summary
|
||||
|
||||
| Where | Rights holder | Our MIT license covers it? | If you fork |
|
||||
|---|---|---|---|
|
||||
| [`assets/illustrations/`](#1-commissioned-artwork--licensed-to-bluesky-only) | Owen D. Pomery, via Brilliant Artists Ltd | No | Replace |
|
||||
| [`assets/icons/`](#2-licensed-icon-system--not-ours-to-pass-on) (top level), Central icon glyphs in `bskyembed/assets/` except the Starter Pack mark | Iconists (David & Storm GbR) | No | Source your own |
|
||||
| [Bluesky marks](#3-bluesky-trademarks-and-brand-assets) — app icons, logos, favicons | Bluesky Social PBC | No | Replace |
|
||||
| [`assets/kawaii.png`, `assets/kawaii_smol.png`](#4-community-and-contest-artwork--credited-but-not-ours-to-license) | [@sawaratsuki.bsky.social](https://bsky.app/profile/sawaratsuki.bsky.social) | No | Replace or remove |
|
||||
| [`assets/icons/custom_logo_japan.svg`](#4-community-and-contest-artwork--credited-but-not-ours-to-license) | A Bluesky Japan logo contest entrant | No | Replace or remove |
|
||||
| [`assets/icons/apple_logo.svg`](#5-third-party-trademarks) | Apple Inc. | No | Rests on your own basis |
|
||||
| [`assets/icons/android_logo.svg`](#5-third-party-trademarks) | Google LLC | No | Rests on your own basis |
|
||||
| [`assets/icons/community/`](#5-third-party-trademarks) | Leaflet, Offprint, pckt, Standard.site, Germ Network | No | Rests on your own basis |
|
||||
| [`assets/fonts/inter/`](#6-third-party-assets-you-may-redistribute), Inter files in `bskyogcard/src/assets/fonts/` | The Inter Project Authors | Separate — OFL 1.1 | **Keep, with the notice** |
|
||||
| [Noto fonts downloaded by `bskyogcard/scripts/install-fonts.ts`](#6-third-party-assets-you-may-redistribute) | Adobe, Google LLC, and The Noto Project Authors | Separate — OFL 1.1 | **Keep, with the notice** |
|
||||
| [`assets/icons/flags/`](#6-third-party-assets-you-may-redistribute) | @catamphetamine | Separate — MIT | **Keep, with the license** |
|
||||
| [`bskyweb/static/media/MaterialIcons.*.ttf`](#6-third-party-assets-you-may-redistribute) | Google, Inc. | Separate — Apache 2.0 | **Keep, with the notice** |
|
||||
| [`assets/images/`](#7-product-imagery--provenance-being-documented) | Mixed, and not yet fully documented — see Section 7 | No | Replace or ship without |
|
||||
|
||||
Everything in [Section 6](#6-third-party-assets-you-may-redistribute) is already permissively licensed. It is the largest group of files listed here and it needs no action from you beyond keeping the notices in place.
|
||||
|
||||
Assets are scoped by directory wherever possible, so that adding a file to a carved-out directory does not require an edit here. Individual paths are listed only where an asset does not sit in a dedicated directory.
|
||||
|
||||
---
|
||||
|
||||
## 1. Commissioned artwork — licensed to Bluesky only
|
||||
|
||||
**`assets/illustrations/`**
|
||||
|
||||
The landing-screen illustration, in light and dark variants (`assets/illustrations/illustration-mobile.png` and `assets/illustrations/illustration-mobile-dark.png`), used by `src/view/com/auth/SplashScreen.tsx`.
|
||||
|
||||
**Rights holder: Owen D. Pomery**, represented by Brilliant Artists Ltd. Bluesky Social PBC commissioned the work and holds a usage license. Copyright remains with the artist. Our license is limited to Bluesky's own products and channels, is exclusive to us, and does not permit us to sublicense the artwork or to distribute modified versions of it.
|
||||
|
||||
**If you are forking this repository, replace these files.** Because our license is exclusive, the artwork is not available for separate third-party licensing while that license runs. Please do not approach the artist or his agent for permission — the constraint is our agreement, not their willingness. If you have already shipped it, contact us and we will help you sort it out rather than leaving you to guess.
|
||||
|
||||
See [`assets/illustrations/README.md`](./assets/illustrations/README.md).
|
||||
|
||||
## 2. Licensed icon system — not ours to pass on
|
||||
|
||||
**`assets/icons/` (top level), and the Central icon glyphs in `bskyembed/assets/`, except `bskyembed/assets/starterPack.svg`**
|
||||
|
||||
**Rights holder: Iconists (David & Storm GbR).** The user-interface glyphs come from their [Central icon system](https://iconists.co/central). Bluesky Social PBC licenses them for use in our own products. **That license is for our own use. It does not include the right to pass any rights to the icons on to you.**
|
||||
|
||||
The fact that we have our own license does not mean that you cannot use these icons. It means that any right you have to use them has to come from Iconists, not us. Licenses are available from [iconists.co](https://iconists.co), and there are openly licensed alternatives if you prefer that.
|
||||
|
||||
This section covers every file at the top level of `assets/icons/` **except** those named elsewhere in this document — specifically `assets/icons/logomark.svg`, `assets/icons/newskie.svg`, `assets/icons/verifiedCheck.svg`, `assets/icons/verifierCheck.svg`, `assets/icons/starterPack.svg`, `assets/icons/starterPack_stroke2_corner0_rounded.svg`, `assets/icons/custom_logo_japan.svg`, `assets/icons/apple_logo.svg`, and `assets/icons/android_logo.svg`. The `assets/icons/flags/` and `assets/icons/community/` subdirectories are covered by [Section 6](#6-third-party-assets-you-may-redistribute) and [Section 5](#5-third-party-trademarks) respectively.
|
||||
|
||||
See [`assets/icons/README.md`](./assets/icons/README.md).
|
||||
|
||||
## 3. Bluesky trademarks and brand assets
|
||||
|
||||
**Rights holder: Bluesky Social PBC.** Our name, logo, butterfly mark, logotype, and app icons are our trademarks. They are not licensed to you under the MIT license or by this document. Use of them is governed by our [Trademark Policy](https://bsky.social/about/support/trademarks) and [Brand Guidelines](https://bsky.social/about/support/branding).
|
||||
|
||||
You may refer to Bluesky by name to describe interoperability or origin — for example, "a client for Bluesky," or "based on the Bluesky app." You may not use our marks as the identity of your own product or service, or in any way likely to suggest that Bluesky publishes, endorses, or supports it.
|
||||
|
||||
- `assets/app-icons/` — all iOS and Android app icon variants, including the `.icon` bundles
|
||||
- `assets/favicon.png`
|
||||
- `assets/logo.png`
|
||||
- `assets/default-avatar.png`
|
||||
- `assets/icon-android-foreground.png`
|
||||
- `assets/icon-android-monochrome.png`
|
||||
- `assets/icon-android-notification.png`
|
||||
- `assets/splash/splash.png`
|
||||
- `assets/splash/splash-dark.png`
|
||||
- `assets/splash/android-splash-logo-white.png`
|
||||
- `assets/icons/logomark.svg`
|
||||
- `assets/icons/newskie.svg`
|
||||
- `assets/icons/verifiedCheck.svg`
|
||||
- `assets/icons/verifierCheck.svg`
|
||||
- `assets/icons/starterPack.svg`
|
||||
- `assets/icons/starterPack_stroke2_corner0_rounded.svg`
|
||||
- `bskyembed/assets/logo.svg`
|
||||
- `bskyembed/assets/logo_full_name.svg`
|
||||
- `bskyembed/assets/starterPack.svg`
|
||||
- `bskyweb/static/favicon.png`
|
||||
- `bskyweb/static/favicon-16x16.png`
|
||||
- `bskyweb/static/favicon-32x32.png`
|
||||
- `bskyweb/static/apple-touch-icon.png`
|
||||
- `bskyweb/static/safari-pinned-tab.svg`
|
||||
- `bskyweb/static/social-card-default.png`
|
||||
- `bskyweb/static/social-card-default-gradient.png`
|
||||
- `bskyweb/embedr-static/favicon.png`
|
||||
- `bskyweb/embedr-static/favicon-16x16.png`
|
||||
- `bskyweb/embedr-static/favicon-32x32.png`
|
||||
- `modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/`
|
||||
- Inline vector path data in `src/view/icons/Logo.tsx`, `src/view/icons/Logomark.tsx`, `src/view/icons/LogomarkWithType.tsx`, and `src/view/icons/Logotype.tsx`
|
||||
|
||||
These files stay in this repository because the app needs them to build. **If you fork, replace them with your own** — that is the one thing this section asks of you. Shipping an app that looks like Bluesky is also a problem under the app stores' own rules on copycat apps, quite apart from trademark.
|
||||
|
||||
## 4. Community and contest artwork — credited, but not ours to license
|
||||
|
||||
These are third-party artworks that appear in the app with attribution. We hold no license that lets us pass rights to them on to you.
|
||||
|
||||
- `assets/kawaii.png` and `assets/kawaii_smol.png` — **rights holder:
|
||||
[@sawaratsuki.bsky.social](https://bsky.app/profile/sawaratsuki.bsky.social)**. Shown as an opt-in variant and credited in `src/view/shell/Drawer.tsx` and `src/view/shell/desktop/RightNav.tsx`.
|
||||
- `assets/icons/custom_logo_japan.svg` — **rights holder: the entrant who won the Bluesky Japan logo contest.**
|
||||
|
||||
Replace or remove these if you fork. If you want to use them, contact the artist.
|
||||
|
||||
## 5. Third-party trademarks
|
||||
|
||||
These marks belong to other companies. We include them to identify their services in our UI — sign-in buttons, store badges, and links to third-party applications. We are neither granting nor withholding permission, because it is not ours to give. Your use of them rests on your own nominative-use basis or on permission from the mark owner.
|
||||
|
||||
- `assets/icons/apple_logo.svg` — **Apple Inc.**
|
||||
- `assets/icons/android_logo.svg` — **Google LLC**
|
||||
- `assets/icons/community/leaflet.svg` — **Leaflet**
|
||||
- `assets/icons/community/offprint.svg` — **Offprint**
|
||||
- `assets/icons/community/pckt.svg` and `assets/icons/community/pckt-full.svg` — **pckt**
|
||||
- `assets/icons/community/standard-site.svg` — **Standard.site**
|
||||
- `assets/icons/community/germ_logo.webp` — **Germ Network**
|
||||
|
||||
Apple's and Google's marks in particular carry their own brand guidelines governing size, spacing, and permitted contexts. If you ship a sign-in button or a store badge, follow their guidelines.
|
||||
|
||||
## 6. Third-party assets you may redistribute
|
||||
|
||||
These are licensed on terms that permit redistribution. Nothing in this document restricts them. We list them so you know they are safe, and so you know to carry their notices. This is the largest group of assets in this document.
|
||||
|
||||
| Asset | Path | Rights holder | License | Notice |
|
||||
|---|---|---|---|---|
|
||||
| Inter typeface | `assets/fonts/inter/`, `bskyogcard/src/assets/fonts/Inter-*.ttf` | The Inter Project Authors | SIL Open Font License 1.1 | [`OFL.txt`](./assets/fonts/inter/OFL.txt) |
|
||||
| Noto Sans families (OG card service) | Downloaded by `bskyogcard/scripts/install-fonts.ts` | Adobe, Google LLC, and The Noto Project Authors | SIL Open Font License 1.1 | [`README.md`](./bskyogcard/src/assets/fonts/README.md) |
|
||||
| country-flag-icons | `assets/icons/flags/` | @catamphetamine | MIT | [`LICENSE`](./assets/icons/flags/LICENSE) |
|
||||
| Material Icons | `bskyweb/static/media/MaterialIcons.*.ttf` | Google, Inc. | Apache License 2.0 | [`NOTICE.md`](./NOTICE.md) |
|
||||
|
||||
Build output under `bskyweb/static/media/` also contains compiled Inter files. They are the same OFL-licensed typeface, emitted by the web build. The bundled Inter license does not designate a Reserved Font Name.
|
||||
|
||||
The OG card build downloads Noto Sans fonts into `bskyogcard/src/assets/fonts/` and copies them into its build output. Their copyright notices and OFL text are in [`bskyogcard/src/assets/fonts/OFL-NOTO.txt`](./bskyogcard/src/assets/fonts/OFL-NOTO.txt). The CJK fonts reserve the name "Source."
|
||||
|
||||
See [`NOTICE.md`](./NOTICE.md) for the consolidated third-party notices.
|
||||
|
||||
## 7. Product imagery — provenance being documented
|
||||
|
||||
**`assets/images/`**
|
||||
|
||||
Product illustration and announcement imagery — onboarding art, chat backgrounds, feature announcement graphics, and similar.
|
||||
|
||||
**Rights holder: mixed, and we have not finished documenting it.** Some of this is Bluesky's own work. Some was commissioned from outside illustrators, on terms that do not let us pass rights on. We are working out which is which.
|
||||
|
||||
Until we have, **treat the whole directory as outside the MIT license and not licensed for your use.**
|
||||
|
||||
When this is resolved, one of two things will happen: this section will name the rights holder for each file, or the directory will be split so that the boundary itself carries the answer. If you need a specific file's status before then, ask us and we will find out.
|
||||
|
||||
If you are forking, replace these or ship without them. See [`assets/images/README.md`](./assets/images/README.md).
|
||||
|
||||
---
|
||||
|
||||
## If you are forking
|
||||
|
||||
You have our blessing to fork this application. These steps map one-to-one to the sections above.
|
||||
|
||||
1. **Replace `assets/illustrations/`** — commissioned artwork, licensed to Bluesky only. [Section 1](#1-commissioned-artwork--licensed-to-bluesky-only)
|
||||
2. **Source your own UI icons** — the glyph set in `assets/icons/` is licensed to us for our own use. [Section 2](#2-licensed-icon-system--not-ours-to-pass-on)
|
||||
3. **Replace the Bluesky marks** — app icons, favicons, logo files, and the inline logo paths in `src/view/icons/`. [Section 3](#3-bluesky-trademarks-and-brand-assets)
|
||||
4. **Replace or remove the community and contest artwork.** [Section 4](#4-community-and-contest-artwork--credited-but-not-ours-to-license)
|
||||
5. **Check your own position on the third-party marks.** [Section 5](#5-third-party-trademarks)
|
||||
6. **Keep the assets you may redistribute, and keep their notices with them.** [Section 6](#6-third-party-assets-you-may-redistribute)
|
||||
7. **Replace `assets/images/`, or ship without it.** [Section 7](#7-product-imagery--provenance-being-documented)
|
||||
|
||||
Then change your branding, support links, and analytics as described in the [Forking guidelines](./README.md#forking-guidelines). That part is not about licensing — it is what makes a fork clearly distinguishable from Bluesky, which matters both for your users and for app store review.
|
||||
|
||||
## Questions
|
||||
|
||||
If something in this repository looks like it should be on this list and is not, if a rights holder named here is wrong, or if you are unsure whether an asset is covered, open an issue or email [atmosphere@blueskyweb.xyz](mailto:atmosphere@blueskyweb.xyz).
|
||||
|
||||
## History
|
||||
|
||||
- **August 2026** — this document added, along with [`NOTICE.md`](./NOTICE.md), per-directory notices, and the required Apache 2.0 and OFL license texts. It documents pre-existing rights; it does not change the [MIT license](./LICENSE) or relicense any file.
|
||||
- **Before that** — the repository carried a blanket MIT license with no asset carve-out, and the forking guidelines did not mention commissioned artwork, trademarks, or licensed icons.
|
||||
|
||||
---
|
||||
|
||||
*This document describes the licensing position of assets in this repository. It is not a grant of rights, and it does not modify the [MIT license](./LICENSE) as it applies to source code.*
|
||||
@@ -224,6 +224,7 @@ return conditional styles inline in a style array: `web({cursor: 'pointer'})`,
|
||||
- Prefer prop destructuring via parameters over a const within the component.
|
||||
- Prefer inline types over `Props` types or interfaces.
|
||||
- Set reasonable defaults for optional props.
|
||||
- Prefer the implicit global `React` for types over `type` imports.
|
||||
|
||||
```tsx
|
||||
import {Fragment} from 'react'
|
||||
@@ -232,7 +233,13 @@ import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
function MyComponent({items = []}: {items?: string[]}) {
|
||||
function MyComponent({
|
||||
items = [],
|
||||
children,
|
||||
}: {
|
||||
items?: string[]
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
@@ -247,6 +254,7 @@ function MyComponent({items = []}: {items?: string[]}) {
|
||||
<Text>{item}</Text>
|
||||
</Fragment>
|
||||
))}
|
||||
{children}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.18.0-alpine3.23 AS build
|
||||
FROM node:24.19.0-alpine3.23 AS build
|
||||
|
||||
# Move files into the image and install
|
||||
WORKDIR /app
|
||||
@@ -14,7 +14,7 @@ RUN yarn build
|
||||
RUN yarn install --production --ignore-scripts --prefer-offline
|
||||
|
||||
# Uses assets from build stage to reduce build size
|
||||
FROM node:24.18.0-alpine3.23
|
||||
FROM node:24.19.0-alpine3.23
|
||||
|
||||
RUN apk add --update dumb-init
|
||||
|
||||
@@ -26,8 +26,9 @@ COPY --from=build /app /app
|
||||
RUN mkdir /app/data && chown node /app/data
|
||||
|
||||
VOLUME /app/data
|
||||
EXPOSE 3000
|
||||
EXPOSE 3000 9090
|
||||
ENV LINK_PORT=3000
|
||||
ENV LINK_METRICS_PORT=9090
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.18.0-alpine3.23 AS build
|
||||
FROM node:24.19.0-alpine3.23 AS build
|
||||
|
||||
# Tells pnpm to run non-interactively (needed for install/script steps)
|
||||
ENV CI=true
|
||||
@@ -9,7 +9,7 @@ WORKDIR /app
|
||||
COPY ./bskyogcard/package.json ./
|
||||
COPY ./bskyogcard/pnpm-lock.yaml ./
|
||||
COPY ./bskyogcard/pnpm-workspace.yaml ./
|
||||
RUN npm install --global pnpm@11.13.1
|
||||
RUN npm install --global pnpm@11.21.0
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
COPY ./bskyogcard ./
|
||||
@@ -19,7 +19,7 @@ RUN pnpm install-fonts && pnpm build
|
||||
RUN pnpm install --prod --ignore-scripts --prefer-offline
|
||||
|
||||
# Uses assets from build stage to reduce build size
|
||||
FROM node:24.18.0-alpine3.23
|
||||
FROM node:24.19.0-alpine3.23
|
||||
|
||||
RUN apk add --update dumb-init
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ WORKDIR /usr/src/social-app
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Node
|
||||
ENV NODE_VERSION=24.18.0
|
||||
ENV NODE_VERSION=24.19.0
|
||||
ENV NVM_DIR=/usr/share/nvm
|
||||
|
||||
# Go
|
||||
@@ -33,7 +33,7 @@ RUN mkdir --parents $NVM_DIR && \
|
||||
RUN \. "$NVM_DIR/nvm.sh" && \
|
||||
nvm install $NODE_VERSION && \
|
||||
nvm use $NODE_VERSION && \
|
||||
npm install --global pnpm@11.13.1 && \
|
||||
npm install --global pnpm@11.21.0 && \
|
||||
pnpm install --frozen-lockfile && \
|
||||
cd bskyembed && pnpm install --frozen-lockfile && cd .. && \
|
||||
pnpm intl:build && \
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Third-party notices
|
||||
|
||||
This file collects the attribution and license notices that third-party components in this repository require us to carry. It is separate from [`ASSETS.md`](./ASSETS.md), which describes which assets our [MIT license](./LICENSE) does and does not cover.
|
||||
|
||||
If you distribute this software or a fork of it, these notices need to travel with it.
|
||||
|
||||
---
|
||||
|
||||
## Material Icons
|
||||
|
||||
**Path:** `bskyweb/static/media/MaterialIcons.*.ttf`
|
||||
**License:** Apache License, Version 2.0 — full text at [`licenses/APACHE-2.0.txt`](./licenses/APACHE-2.0.txt)
|
||||
|
||||
```
|
||||
Copyright 2018 Google, Inc. All Rights Reserved.
|
||||
```
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
*This font is emitted into the web build by `@expo/vector-icons`, which the Expo toolchain pulls in transitively. It is a distributed artifact rather than a source asset, which is why the notice lives here rather than beside the file.*
|
||||
|
||||
## Inter
|
||||
|
||||
**Paths:** `assets/fonts/inter/`, `bskyogcard/src/assets/fonts/Inter-*.ttf`, and compiled copies under `bskyweb/static/media/`
|
||||
**License:** SIL Open Font License, Version 1.1 — full text at [`assets/fonts/inter/OFL.txt`](./assets/fonts/inter/OFL.txt)
|
||||
|
||||
```
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
```
|
||||
|
||||
Inter is licensed under the SIL Open Font License, Version 1.1. The bundled license does not designate a Reserved Font Name.
|
||||
|
||||
## Noto Sans
|
||||
|
||||
**Generated by:** `bskyogcard/scripts/install-fonts.ts`, into `bskyogcard/src/assets/fonts/` and the OG card build output
|
||||
**License:** SIL Open Font License, Version 1.1 — full text at [`bskyogcard/src/assets/fonts/OFL-NOTO.txt`](./bskyogcard/src/assets/fonts/OFL-NOTO.txt)
|
||||
|
||||
```
|
||||
(c) 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'.
|
||||
Copyright 2015-2020 Google LLC. All Rights Reserved.
|
||||
Copyright 2024 The Noto Project Authors (https://github.com/notofonts/hebrew)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/thai)
|
||||
```
|
||||
|
||||
The OG card build downloads Noto Sans Arabic, Hebrew, HK, JP, KR, SC, TC, and Thai from Fontsource. All are licensed under OFL 1.1. The CJK families reserve the name "Source."
|
||||
|
||||
## country-flag-icons
|
||||
|
||||
**Path:** `assets/icons/flags/`
|
||||
**License:** MIT — full text at [`assets/icons/flags/LICENSE`](./assets/icons/flags/LICENSE)
|
||||
|
||||
```
|
||||
Copyright (c) 2020 @catamphetamine <purecatamphetamine@gmail.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For the licensing position of assets that are **not** covered by our MIT license — commissioned artwork, the licensed icon system, Bluesky trademarks, third-party marks, and the product imagery in `assets/images/` — see [`ASSETS.md`](./ASSETS.md), which names known rights holders and identifies the product imagery whose provenance is still being documented.
|
||||
@@ -57,6 +57,15 @@ Please be sure to:
|
||||
- Change all branding in the repository and UI to clearly differentiate from Bluesky.
|
||||
- Change any support links (feedback, email, terms of service, etc) to your own systems.
|
||||
- Replace any analytics or error-collection systems with your own so we don't get super confused.
|
||||
- Replace the landing-screen illustration in `assets/illustrations/`. It is commissioned artwork licensed to Bluesky alone, and our MIT license does not cover it.
|
||||
- Source your own UI icons. The glyph set in `assets/icons/` is licensed to us by a third party for our own use, and that license does not extend to you.
|
||||
- Replace the Bluesky logo, app icons, and other brand assets. Our trademarks are not licensed with the code.
|
||||
|
||||
Please read [./ASSETS.md](./ASSETS.md) before you ship. Not every file in this repository is
|
||||
covered by our MIT license — some of the artwork, icons, fonts, and brand assets are licensed to
|
||||
us by third parties or are trademarks, and `ASSETS.md` says which ones and what to do about them.
|
||||
That file is new. Its absence is why some forks have shipped assets they did not have rights to,
|
||||
and that was our omission rather than theirs.
|
||||
|
||||
## Security disclosures
|
||||
|
||||
@@ -68,7 +77,11 @@ Bluesky is an open social network built on the AT Protocol, a flexible technolog
|
||||
|
||||
## License (MIT)
|
||||
|
||||
See [./LICENSE](./LICENSE) for the full license.
|
||||
See [./LICENSE](./LICENSE) for the full license, which covers the source code in this repository.
|
||||
|
||||
It does not cover every file. Certain images, icons, fonts, and brand assets are licensed to us
|
||||
by third parties, or are trademarks, and are carved out — see [./ASSETS.md](./ASSETS.md). Required
|
||||
third-party attribution notices are collected in [./NOTICE.md](./NOTICE.md).
|
||||
|
||||
Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge).
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {RichText} from '@atproto/api'
|
||||
import {RichText} from '@bsky/sdk/richtext'
|
||||
import {i18n} from '@lingui/core'
|
||||
|
||||
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
|
||||
|
||||
@@ -184,10 +184,6 @@ module.exports = function (_config) {
|
||||
androidStatusBar: {
|
||||
barStyle: 'light-content',
|
||||
},
|
||||
// Dark nav bar in light mode is better than light nav bar in dark mode
|
||||
androidNavigationBar: {
|
||||
barStyle: 'light-content',
|
||||
},
|
||||
android: {
|
||||
icon: './assets/app-icons/android_icon_default_next.png',
|
||||
adaptiveIcon: {
|
||||
@@ -261,7 +257,7 @@ module.exports = function (_config) {
|
||||
'expo-build-properties',
|
||||
{
|
||||
ios: {
|
||||
deploymentTarget: '15.1',
|
||||
deploymentTarget: '16.4',
|
||||
buildReactNativeFromSource: true,
|
||||
ccacheEnabled: IS_DEV,
|
||||
cxxLanguageStandard: 'c++23',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
**Rights holder: Bluesky Social PBC.**
|
||||
|
||||
The app icons in this directory are our trademarks — the butterfly mark and its variants, as shipped to the iOS App Store and Google Play.
|
||||
|
||||
**These are not covered by the [MIT license](../../LICENSE) that applies to the rest of this repository.** Use of Bluesky's marks is governed by our [Trademark Policy](https://bsky.social/about/support/trademarks) and [Brand Guidelines](https://bsky.social/about/support/branding), not by the license on our source code.
|
||||
|
||||
You may say that your app is a client for Bluesky, or that it is based on the Bluesky app. You may not use our marks as the identity of your own product, or in any way likely to suggest that Bluesky publishes, endorses, or supports it.
|
||||
|
||||
If you are forking this repository, replace these files with your own icons. Shipping an app that looks like Bluesky is also a problem under the app stores' own rules on copycat apps, quite apart from trademark.
|
||||
|
||||
See [`ASSETS.md`](../../ASSETS.md#3-bluesky-trademarks-and-brand-assets) for the full asset licensing picture.
|
||||
@@ -0,0 +1,92 @@
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org/
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,13 @@
|
||||
**Rights holder: The Inter Project Authors.**
|
||||
|
||||
[Inter](https://rsms.me/inter/) is a typeface by Rasmus Andersson, licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
```
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
```
|
||||
|
||||
The full license text is in [`OFL.txt`](./OFL.txt) and must travel with these files.
|
||||
|
||||
**You may redistribute these files under the OFL.** They are separately licensed and are not covered by Bluesky's [MIT license](../../../LICENSE). Keep [`OFL.txt`](./OFL.txt) with redistributed copies.
|
||||
|
||||
The bundled OFL does not designate a Reserved Font Name.
|
||||
@@ -0,0 +1,18 @@
|
||||
**Rights holder: Iconists (David & Storm GbR).**
|
||||
|
||||
The user-interface glyphs in this directory come from their [Central icon system](https://iconists.co/central). Bluesky Social PBC licenses them for use in our own products. **That license is for our own use, and it does not extend to you.**
|
||||
|
||||
**These icons are not covered by the [MIT license](../../LICENSE) that applies to the rest of this repository.** The fact that we have our own license does not mean that you cannot use these icons. It means that any right you have to use them has to come from Iconists, not us. Licenses are available from [iconists.co](https://iconists.co), and there are openly licensed alternatives if you prefer that.
|
||||
|
||||
This notice covers the SVG files at the top level of this directory. It does not cover:
|
||||
|
||||
| Not covered here | Rights holder | See |
|
||||
|---|---|---|
|
||||
| `flags/` | @catamphetamine, MIT licensed | [`flags/README.md`](./flags/README.md) |
|
||||
| `community/` | Third-party services | [`community/README.md`](./community/README.md) |
|
||||
| `logomark.svg`, `newskie.svg`, `verifiedCheck.svg`, `verifierCheck.svg`, `starterPack.svg`, `starterPack_stroke2_corner0_rounded.svg` | Bluesky Social PBC — trademarks | [`ASSETS.md`](../../ASSETS.md#3-bluesky-trademarks-and-brand-assets) |
|
||||
| `custom_logo_japan.svg` | A Bluesky Japan logo contest entrant | [`ASSETS.md`](../../ASSETS.md#4-community-and-contest-artwork--credited-but-not-ours-to-license) |
|
||||
| `apple_logo.svg` | Apple Inc. | [`ASSETS.md`](../../ASSETS.md#5-third-party-trademarks) |
|
||||
| `android_logo.svg` | Google LLC | [`ASSETS.md`](../../ASSETS.md#5-third-party-trademarks) |
|
||||
|
||||
Adding an icon here? If it came from Central, this notice covers it. If it came from anywhere else, add it to [`ASSETS.md`](../../ASSETS.md) so the notice does not go stale.
|
||||
@@ -0,0 +1,15 @@
|
||||
The icons in this directory are the marks of third-party services that appear in the Bluesky app.
|
||||
|
||||
| Icon | Rights holder |
|
||||
|---|---|
|
||||
| `leaflet.svg` | [Leaflet](https://leaflet.pub) |
|
||||
| `offprint.svg` | [Offprint](https://offprint.net) |
|
||||
| `pckt.svg`, `pckt-full.svg` | [pckt](https://pckt.blog) |
|
||||
| `standard-site.svg` | [Standard.site](https://standard.site) |
|
||||
| `germ_logo.webp` | [Germ Network](https://germnetwork.com) |
|
||||
|
||||
**These marks belong to their respective owners.** They are not Bluesky trademarks, they are not covered by the [MIT license](../../../LICENSE) that applies to the rest of this repository, and we are not in a position to grant or withhold permission to use them. We include them to identify those services in our UI.
|
||||
|
||||
If you fork this repository and keep these icons, your use of them rests on your own nominative-use basis or on permission from the mark owner.
|
||||
|
||||
See [`ASSETS.md`](../../../ASSETS.md#5-third-party-trademarks) for the full asset licensing picture.
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 @catamphetamine <purecatamphetamine@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,4 +1,9 @@
|
||||
**Rights holder: @catamphetamine.**
|
||||
|
||||
The flags in this directory are from the excellent [country-flag-icons](https://gitlab.com/catamphetamine/country-flag-icons).
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2020 @catamphetamine <purecatamphetamine@gmail.com>
|
||||
|
||||
**You may redistribute these files under country-flag-icons' MIT license, which is separate from Bluesky's repository license.** The complete license is in [`LICENSE`](./LICENSE) and must travel with redistributed copies.
|
||||
|
||||
See [`ASSETS.md`](../../../ASSETS.md#6-third-party-assets-you-may-redistribute) for the full asset licensing picture.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Zm1 16V5h14v14H5Zm10.725-5.2c0 .566-.283.872-.802.872-.538 0-.848-.318-.848-.872v-3.635c0-.512.314-.826.82-.826h2.496c.35 0 .609.272.609.64 0 .369-.26.629-.609.629h-1.666v.973h1.47c.365 0 .608.248.608.613 0 .36-.247.613-.608.613h-1.47v.993Zm-3.367.872c.526 0 .813-.31.813-.872v-3.627c0-.558-.295-.873-.825-.873s-.825.31-.825.873V13.8c0 .558.302.872.837.872Zm-3.879.078C6.92 14.75 6 13.827 6 12.287v-.617c0-1.47.955-2.42 2.472-2.42.589 0 1.139.147 1.548.388.404.236.664.562.664.915 0 .373-.271.636-.656.636a.8.8 0 0 1-.41-.108 2 2 0 0 1-.271-.177c-.208-.148-.421-.3-.746-.3-.644 0-.95.38-.95 1.155v.52c0 .768.306 1.168.903 1.168.436 0 .735-.248.735-.61v-.061h-.146c-.412 0-.632-.194-.632-.551 0-.353.216-.535.632-.535h.806c.617 0 .884.256.884.834v.166c0 1.253-.92 2.06-2.354 2.06Z" clip-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 992 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6 12.287c0 1.54.92 2.463 2.48 2.463 1.434 0 2.353-.807 2.353-2.06v-.166c0-.578-.267-.834-.884-.834h-.806c-.416 0-.632.182-.632.535 0 .357.22.55.632.55h.146v.063c0 .36-.299.609-.735.609-.597 0-.904-.4-.904-1.168v-.52c0-.775.307-1.155.951-1.155.464 0 .7.31 1.018.477a.8.8 0 0 0 .409.108c.385 0 .656-.263.656-.636 0-.353-.26-.679-.664-.915-.409-.24-.96-.388-1.548-.388C6.955 9.25 6 10.2 6 11.67v.617Zm6.358 2.385c.526 0 .813-.31.813-.872v-3.627c0-.558-.295-.873-.825-.873s-.825.31-.825.873V13.8c0 .558.302.872.837.872Zm2.565 0c.519 0 .802-.306.802-.872v-.993h1.47c.361 0 .608-.252.608-.613 0-.365-.243-.613-.608-.613h-1.47v-.973h1.666c.35 0 .609-.26.609-.629 0-.368-.26-.64-.609-.64h-2.495c-.507 0-.821.314-.821.826V13.8c0 .554.31.872.848.872ZM19 7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7Zm2 10a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v10Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z" clip-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 514 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M19 15.355c-1.112.696-2.385 1.1-4 1.1-2.81 0-4.796-1.756-6.312-3.39a1 1 0 0 0-1.273-.16L5 14.71V17a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1.646ZM16 9a1 1 0 1 0-2 0 1 1 0 0 0 2 0Zm3-2a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v5.213l1.246-.932.044-.031a3 3 0 0 1 3.862.455c1.468 1.581 2.942 2.75 4.848 2.75 1.704 0 2.854-.558 4-1.621V7Zm-1 2a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm3 8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v10Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 563 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M2.293 2.293a1 1 0 0 1 1.414 0l18 18a1 1 0 0 1-1.414 1.414L17.586 19H6.414l1.293 1.293a1 1 0 0 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 1 1 1.414 1.414L6.414 17h9.172L5.909 7.323A2 2 0 0 0 5 9v2a1 1 0 0 1-2 0V9c0-1.255.579-2.372 1.482-3.104L2.293 3.707a1 1 0 0 1 0-1.414M20 12a1 1 0 0 1 1 1v2a1 1 0 0 1-2 0v-2a1 1 0 0 1 1-1m-3.707-9.707a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H11a1 1 0 0 1 0-2h6.586l-1.293-1.293a1 1 0 0 1 0-1.414"/></svg>
|
||||
|
After Width: | Height: | Size: 575 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M10.775 2.74a3 3 0 0 1 3.524 2.158l.57 2.13a7.5 7.5 0 1 1-6.12 10.744l-2.228.598-.15.036a3 3 0 0 1-3.48-2.009l-.044-.148L.777 8.52A3 3 0 0 1 2.75 4.89l.148-.044 7.728-2.07.149-.037Zm4.624 6.262.97 3.624.037.149a3 3 0 0 1-2.009 3.48l-.148.044-3.517.942A5.5 5.5 0 1 0 15.5 9l-.101.002Zm-3.031-3.586a1 1 0 0 0-1.225-.707l-7.727 2.07a1 1 0 0 0-.707 1.225l2.07 7.727a1 1 0 0 0 1.225.707l2.123-.57a7.504 7.504 0 0 1 4.788-8.412l-.547-2.04ZM13.435 9.4a5.5 5.5 0 0 0-3.37 5.948l3.666-.981a1 1 0 0 0 .707-1.225L13.435 9.4Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 656 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 153 133"><path fill="url(#a)" fill-rule="evenodd" d="m60.196 105.445-18.1 4.85c-11.73 3.143-23.788-3.819-26.931-15.55L1.19 42.597c-3.143-11.731 3.819-23.79 15.55-26.932L68.889 1.69C80.62-1.452 92.68 5.51 95.821 17.241l4.667 17.416a50 50 0 0 1 3.522-.125c27.053 0 48.984 21.931 48.984 48.984S131.063 132.5 104.01 132.5c-19.17 0-35.769-11.012-43.814-27.055ZM19.457 25.804 71.606 11.83c6.131-1.643 12.434 1.996 14.076 8.127l4.44 16.571c-20.289 5.987-35.096 24.758-35.096 46.988 0 4.157.517 8.193 1.492 12.047l-17.138 4.593c-6.131 1.642-12.434-1.996-14.077-8.128L11.33 39.88c-1.643-6.131 1.996-12.434 8.127-14.077Zm83.812 19.232q.369-.007.741-.007c21.256 0 38.487 17.231 38.487 38.487s-17.231 38.488-38.487 38.488c-14.29 0-26.76-7.788-33.4-19.35l23.635-6.333c11.731-3.143 18.693-15.2 15.55-26.932l-6.526-24.353Zm-10.428 1.638 6.815 25.432c1.642 6.131-1.996 12.434-8.128 14.076l-24.867 6.664a38.6 38.6 0 0 1-1.139-9.33c0-17.372 11.51-32.056 27.32-36.842Z" clip-rule="evenodd"/><defs><linearGradient id="a" x1="76.715" x2="76.715" y1=".937" y2="132.5" gradientUnits="userSpaceOnUse"><stop stop-color="#0a7aff"/><stop offset="1" stop-color="#59b9ff"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,11 @@
|
||||
**Rights holder: [Owen D. Pomery](https://owenpomery.com/work)**, represented by [Brilliant Artists Ltd](https://brilliantartists.co.uk/).
|
||||
|
||||
The landing screen illustration in this directory was commissioned by Bluesky Social PBC. Copyright in the artwork remains with the artist.
|
||||
|
||||
**This artwork is not covered by the [MIT license](../../LICENSE) that applies to the rest of this repository.** Our license is limited to Bluesky's own products and channels, is exclusive to us, and does not permit us to sublicense the artwork or to distribute modified versions of it.
|
||||
|
||||
If you are forking this repository, replace these files. Because our license is exclusive, the artwork is not available for separate third-party licensing while that license runs. Please do not approach the artist or his agent for permission — the constraint is our agreement, not their willingness. If you have already shipped it, contact us and we will help you sort it out.
|
||||
|
||||
Adding a file here? This directory is carved out of the MIT license, so a new file inherits that treatment. If the file is not commissioned artwork covered by this notice, put it somewhere else.
|
||||
|
||||
See [`ASSETS.md`](../../ASSETS.md) for the full asset licensing picture.
|
||||
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,13 @@
|
||||
**Rights holder: mixed, and we have not finished documenting it.**
|
||||
|
||||
This directory holds product imagery — onboarding art, chat backgrounds, feature announcement graphics, and similar. Some of it is Bluesky's own work. Some was commissioned from outside illustrators, on terms that do not let us pass rights on. We are working out which is which.
|
||||
|
||||
Until we have, **treat the whole directory as outside the [MIT license](../../LICENSE) and not licensed for your use.**
|
||||
|
||||
When this is resolved, one of two things will happen: this notice will name the rights holder for each file, or the directory will be split so that the boundary itself carries the answer. If you need a specific file's status before then, ask us and we will find out.
|
||||
|
||||
If you are forking this repository, replace these files or ship without them.
|
||||
|
||||
Adding a file here? This directory sits outside the MIT license, so a new file inherits that treatment. If you are adding something we do want forks to be able to reuse, put it in a different directory rather than creating an exception here.
|
||||
|
||||
See [`ASSETS.md`](../../ASSETS.md#7-product-imagery--provenance-being-documented) for the full asset licensing picture.
|
||||
@@ -35,7 +35,13 @@ module.exports = function (api) {
|
||||
// cannot use `env` field because it will put them after
|
||||
// the `react-native-worklets/plugin` plugin
|
||||
...(api.env('test')
|
||||
? ['@babel/plugin-transform-class-static-block']
|
||||
? [
|
||||
'@babel/plugin-transform-class-static-block',
|
||||
// Compile `import()` to require so jest (which runs without
|
||||
// `--experimental-vm-modules`) can execute lazily-loaded modules
|
||||
// like `@ipld/dag-cbor` via its moduleNameMapper.
|
||||
'@babel/plugin-transform-dynamic-import',
|
||||
]
|
||||
: []),
|
||||
...(api.env('production') ? ['transform-remove-console'] : []),
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "0.20.11",
|
||||
"@atproto/lexicon": "0.7.1",
|
||||
"preact": "^10.4.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
"@types/node": "^24.12.2",
|
||||
"@vitejs/plugin-legacy": "^8.0.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^9.18.0",
|
||||
|
||||
@@ -11,6 +11,9 @@ importers:
|
||||
'@atproto/api':
|
||||
specifier: 0.20.11
|
||||
version: 0.20.11
|
||||
'@atproto/lexicon':
|
||||
specifier: 0.7.1
|
||||
version: 0.7.1
|
||||
preact:
|
||||
specifier: ^10.4.8
|
||||
version: 10.29.1
|
||||
@@ -20,10 +23,13 @@ importers:
|
||||
version: 9.39.4
|
||||
'@preact/preset-vite':
|
||||
specifier: ^2.10.2
|
||||
version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))
|
||||
version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))
|
||||
'@types/node':
|
||||
specifier: ^24.12.2
|
||||
version: 24.13.3
|
||||
'@vitejs/plugin-legacy':
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(terser@5.47.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))
|
||||
version: 8.0.1(terser@5.47.1)(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))
|
||||
autoprefixer:
|
||||
specifier: ^10.4.19
|
||||
version: 10.5.0(postcss@8.5.14)
|
||||
@@ -62,7 +68,7 @@ importers:
|
||||
version: 8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3)
|
||||
vite:
|
||||
specifier: ^8.0.16
|
||||
version: 8.0.16(jiti@1.21.7)(terser@5.47.1)
|
||||
version: 8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1)
|
||||
vite-bundle-analyzer:
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8
|
||||
@@ -940,6 +946,9 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/node@24.13.3':
|
||||
resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.59.3':
|
||||
resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -2279,6 +2288,9 @@ packages:
|
||||
uint8arrays@5.1.1:
|
||||
resolution: {integrity: sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==}
|
||||
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.1:
|
||||
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3360,19 +3372,19 @@ snapshots:
|
||||
|
||||
'@package-json/types@0.0.12': {}
|
||||
|
||||
'@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))':
|
||||
'@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0)
|
||||
'@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0)
|
||||
'@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))
|
||||
'@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))
|
||||
'@rollup/pluginutils': 5.3.0
|
||||
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0)
|
||||
debug: 4.4.3
|
||||
magic-string: 0.30.21
|
||||
picocolors: 1.1.1
|
||||
vite: 8.0.16(jiti@1.21.7)(terser@5.47.1)
|
||||
vite-prerender-plugin: 0.5.13(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))
|
||||
vite: 8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1)
|
||||
vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))
|
||||
zimmerframe: 1.1.4
|
||||
transitivePeerDependencies:
|
||||
- preact
|
||||
@@ -3387,7 +3399,7 @@ snapshots:
|
||||
|
||||
'@prefresh/utils@1.2.1': {}
|
||||
|
||||
'@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))':
|
||||
'@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@prefresh/babel-plugin': 0.5.3
|
||||
@@ -3395,7 +3407,7 @@ snapshots:
|
||||
'@prefresh/utils': 1.2.1
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
preact: 10.29.1
|
||||
vite: 8.0.16(jiti@1.21.7)(terser@5.47.1)
|
||||
vite: 8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3470,6 +3482,10 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/node@24.13.3':
|
||||
dependencies:
|
||||
undici-types: 7.18.2
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3))(eslint@9.39.4(jiti@1.21.7))(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
@@ -3620,7 +3636,7 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-legacy@8.0.1(terser@5.47.1)(vite@8.0.16(jiti@1.21.7)(terser@5.47.1))':
|
||||
'@vitejs/plugin-legacy@8.0.1(terser@5.47.1)(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0)
|
||||
@@ -3635,7 +3651,7 @@ snapshots:
|
||||
regenerator-runtime: 0.14.1
|
||||
systemjs: 6.15.1
|
||||
terser: 5.47.1
|
||||
vite: 8.0.16(jiti@1.21.7)(terser@5.47.1)
|
||||
vite: 8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4721,6 +4737,8 @@ snapshots:
|
||||
dependencies:
|
||||
multiformats: 13.4.2
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
||||
|
||||
unicode-match-property-ecmascript@2.0.0:
|
||||
@@ -4779,7 +4797,7 @@ snapshots:
|
||||
|
||||
vite-bundle-analyzer@1.3.8: {}
|
||||
|
||||
vite-prerender-plugin@0.5.13(vite@8.0.16(jiti@1.21.7)(terser@5.47.1)):
|
||||
vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1)):
|
||||
dependencies:
|
||||
kolorist: 1.8.0
|
||||
magic-string: 0.30.21
|
||||
@@ -4787,9 +4805,9 @@ snapshots:
|
||||
simple-code-frame: 1.3.0
|
||||
source-map: 0.7.6
|
||||
stack-trace: 1.0.0
|
||||
vite: 8.0.16(jiti@1.21.7)(terser@5.47.1)
|
||||
vite: 8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1)
|
||||
|
||||
vite@8.0.16(jiti@1.21.7)(terser@5.47.1):
|
||||
vite@8.0.16(@types/node@24.13.3)(jiti@1.21.7)(terser@5.47.1):
|
||||
dependencies:
|
||||
lightningcss: 1.32.0
|
||||
picomatch: 4.0.4
|
||||
@@ -4797,6 +4815,7 @@ snapshots:
|
||||
rolldown: 1.0.3
|
||||
tinyglobby: 0.2.17
|
||||
optionalDependencies:
|
||||
'@types/node': 24.13.3
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
terser: 5.47.1
|
||||
|
||||
@@ -466,6 +466,7 @@ function VideoEmbed({content}: {content: AppBskyEmbedVideo.View}) {
|
||||
preload="metadata"
|
||||
// @ts-expect-error https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video#loading
|
||||
loading="lazy"
|
||||
crossorigin="anonymous"
|
||||
aria-label={content.alt || undefined}
|
||||
onClickCapture={evt => evt.stopPropagation()}
|
||||
className="w-full rounded-xl bg-black"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"types": ["vite/client"],
|
||||
"types": ["node", "vite/client"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"lru-cache": "^11.1.0",
|
||||
"pg": "^8.12.0",
|
||||
"pino": "^9.2.0",
|
||||
"prom-client": "^15.1.3",
|
||||
"uint8arrays": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -10,6 +10,7 @@ async function main() {
|
||||
httpLogger.info(
|
||||
{
|
||||
port: cfg.service.port,
|
||||
metricsPort: cfg.service.metricsPort,
|
||||
safelinkEnabled: cfg.service.safelinkEnabled,
|
||||
hasDbUrl: !!cfg.db.url,
|
||||
hasDbMigrationUrl: !!cfg.db.migrationUrl,
|
||||
@@ -33,16 +34,18 @@ async function main() {
|
||||
|
||||
if (link.ctx.cfg.service.safelinkEnabled) {
|
||||
httpLogger.info('Starting Safelink client')
|
||||
link.ctx.safelinkClient.runFetchEvents()
|
||||
void link.ctx.safelinkClient.runFetchEvents()
|
||||
}
|
||||
|
||||
await link.start()
|
||||
httpLogger.info('Link service is running')
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
httpLogger.info('Link service is stopping')
|
||||
await link.destroy()
|
||||
httpLogger.info('Link service is stopped')
|
||||
process.on('SIGTERM', () => {
|
||||
void (async () => {
|
||||
httpLogger.info('Link service is stopping')
|
||||
await link.destroy()
|
||||
httpLogger.info('Link service is stopped')
|
||||
})()
|
||||
})
|
||||
} catch (error) {
|
||||
httpLogger.error(
|
||||
|
||||
@@ -98,13 +98,15 @@ export class SafelinkClient {
|
||||
url: string,
|
||||
pattern: ToolsOzoneSafelinkDefs.PatternType,
|
||||
): Promise<SafelinkRule> {
|
||||
return db.db
|
||||
.selectFrom('safelink_rule')
|
||||
.selectAll()
|
||||
.where('url', '=', url)
|
||||
.where('pattern', '=', pattern)
|
||||
.orderBy('createdAt', 'desc')
|
||||
.executeTakeFirstOrThrow()
|
||||
return db.observeQuery(`resolve_safelink_${pattern}_rule`, () =>
|
||||
db.db
|
||||
.selectFrom('safelink_rule')
|
||||
.selectAll()
|
||||
.where('url', '=', url)
|
||||
.where('pattern', '=', pattern)
|
||||
.orderBy('createdAt', 'desc')
|
||||
.executeTakeFirstOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
private async addRule(db: Database, rule: SafelinkRule) {
|
||||
|
||||
@@ -7,6 +7,7 @@ export type Config = {
|
||||
|
||||
export type ServiceConfig = {
|
||||
port: number
|
||||
metricsPort: number
|
||||
version?: string
|
||||
hostnames: string[]
|
||||
hostnamesSet: Set<string>
|
||||
@@ -33,6 +34,7 @@ export type DbPoolConfig = {
|
||||
|
||||
export type Environment = {
|
||||
port?: number
|
||||
metricsPort?: number
|
||||
version?: string
|
||||
hostnames: string[]
|
||||
appHostname?: string
|
||||
@@ -52,6 +54,7 @@ export type Environment = {
|
||||
export const readEnv = (): Environment => {
|
||||
return {
|
||||
port: envInt('LINK_PORT'),
|
||||
metricsPort: envInt('LINK_METRICS_PORT'),
|
||||
version: envStr('LINK_VERSION'),
|
||||
hostnames: envList('LINK_HOSTNAMES'),
|
||||
appHostname: envStr('LINK_APP_HOSTNAME'),
|
||||
@@ -74,6 +77,7 @@ export const readEnv = (): Environment => {
|
||||
export const envToCfg = (env: Environment): Config => {
|
||||
const serviceCfg: ServiceConfig = {
|
||||
port: env.port ?? 3000,
|
||||
metricsPort: env.metricsPort ?? 9090,
|
||||
version: env.version,
|
||||
hostnames: env.hostnames,
|
||||
hostnamesSet: new Set(env.hostnames),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import assert from 'assert'
|
||||
import {performance} from 'node:perf_hooks'
|
||||
import {
|
||||
Kysely,
|
||||
type KyselyPlugin,
|
||||
@@ -17,6 +18,8 @@ import {default as migrations} from './migrations/index.js'
|
||||
import {DbMigrationProvider} from './migrations/provider.js'
|
||||
import {type DbSchema} from './schema.js'
|
||||
|
||||
const SLOW_QUERY_THRESHOLD_MS = 1000
|
||||
|
||||
export class Database {
|
||||
migrator: Migrator
|
||||
destroyed = false
|
||||
@@ -101,6 +104,51 @@ export class Database {
|
||||
return this.db.isTransaction
|
||||
}
|
||||
|
||||
async observeQuery<T>(
|
||||
operation: string,
|
||||
query: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const poolIdleConnectionsAtStart = this.cfg.pool.idleCount
|
||||
const poolTotalConnectionsAtStart = this.cfg.pool.totalCount
|
||||
const poolWaitingRequestsAtStart = this.cfg.pool.waitingCount
|
||||
const startedAt = performance.now()
|
||||
let poolIdleConnectionsAtThreshold: number | undefined
|
||||
let poolTotalConnectionsAtThreshold: number | undefined
|
||||
let poolWaitingRequestsAtThreshold: number | undefined
|
||||
const slowQueryTimer = setTimeout(() => {
|
||||
poolIdleConnectionsAtThreshold = this.cfg.pool.idleCount
|
||||
poolTotalConnectionsAtThreshold = this.cfg.pool.totalCount
|
||||
poolWaitingRequestsAtThreshold = this.cfg.pool.waitingCount
|
||||
}, SLOW_QUERY_THRESHOLD_MS)
|
||||
slowQueryTimer.unref()
|
||||
try {
|
||||
return await query()
|
||||
} finally {
|
||||
clearTimeout(slowQueryTimer)
|
||||
const durationMs = Math.round(performance.now() - startedAt)
|
||||
if (durationMs >= SLOW_QUERY_THRESHOLD_MS) {
|
||||
log.warn(
|
||||
{
|
||||
durationMs,
|
||||
operation,
|
||||
poolIdleConnectionsAtEnd: this.cfg.pool.idleCount,
|
||||
poolIdleConnectionsAtStart,
|
||||
poolIdleConnectionsAtThreshold,
|
||||
poolStateAtThresholdCaptured:
|
||||
poolWaitingRequestsAtThreshold !== undefined,
|
||||
poolTotalConnectionsAtEnd: this.cfg.pool.totalCount,
|
||||
poolTotalConnectionsAtStart,
|
||||
poolTotalConnectionsAtThreshold,
|
||||
poolWaitingRequestsAtEnd: this.cfg.pool.waitingCount,
|
||||
poolWaitingRequestsAtStart,
|
||||
poolWaitingRequestsAtThreshold,
|
||||
},
|
||||
'slow database query',
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertTransaction() {
|
||||
assert(this.isTransaction, 'Transaction required')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import assert from 'node:assert'
|
||||
import {type AddressInfo} from 'node:net'
|
||||
import {test} from 'node:test'
|
||||
|
||||
import {envToCfg} from './config.js'
|
||||
import {LinkService} from './index.js'
|
||||
|
||||
const testConfig = () =>
|
||||
envToCfg({
|
||||
dbPostgresUrl: 'postgres://localhost:1/blink',
|
||||
hostnames: ['go.bsky.app'],
|
||||
metricsPort: 0,
|
||||
port: 0,
|
||||
safelinkAgentIdentifier: 'test',
|
||||
safelinkAgentPass: 'test',
|
||||
safelinkPdsUrl: 'https://example.com',
|
||||
})
|
||||
|
||||
void test('serves and terminates the Prometheus listener', async () => {
|
||||
const service = await LinkService.create(testConfig())
|
||||
|
||||
try {
|
||||
await service.start()
|
||||
const {port} = service.metricsServer?.address() as AddressInfo
|
||||
const res = await fetch(`http://127.0.0.1:${port}/metrics`)
|
||||
|
||||
assert.strictEqual(res.status, 200)
|
||||
assert.match(res.headers.get('content-type') ?? '', /text\/plain/)
|
||||
const metrics = await res.text()
|
||||
assert.match(metrics, /process_cpu_user_seconds_total/)
|
||||
assert.match(metrics, /nodejs_eventloop_lag_max_seconds/)
|
||||
assert.match(metrics, /bskylink_db_pool_connections\{state="idle"\} 0/)
|
||||
assert.match(metrics, /bskylink_db_pool_connections\{state="in_use"\} 0/)
|
||||
assert.match(metrics, /bskylink_db_pool_max_connections 10/)
|
||||
assert.match(metrics, /bskylink_db_pool_waiting_requests 0/)
|
||||
assert.doesNotMatch(metrics, /http_request_duration_seconds/)
|
||||
} finally {
|
||||
await service.destroy()
|
||||
}
|
||||
|
||||
assert.strictEqual(service.metricsServer?.listening, false)
|
||||
})
|
||||
|
||||
void test('isolates the Prometheus registry per service', async () => {
|
||||
const first = await LinkService.create(testConfig())
|
||||
const second = await LinkService.create(testConfig())
|
||||
|
||||
await Promise.all([first.destroy(), second.destroy()])
|
||||
})
|
||||
@@ -4,10 +4,12 @@ import type http from 'node:http'
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
|
||||
import {type Registry} from 'prom-client'
|
||||
|
||||
import {type Config} from './config.js'
|
||||
import {AppContext} from './context.js'
|
||||
import i18n from './i18n.js'
|
||||
import {createPrometheusRegistry} from './prometheus.js'
|
||||
import {default as routes, errorHandler} from './routes/index.js'
|
||||
|
||||
export * from './config.js'
|
||||
@@ -16,12 +18,17 @@ export * from './logger.js'
|
||||
|
||||
export class LinkService {
|
||||
public server?: http.Server
|
||||
public metricsServer?: http.Server
|
||||
private terminator?: HttpTerminator
|
||||
private metricsTerminator?: HttpTerminator
|
||||
private metricsRegistry: Registry
|
||||
|
||||
constructor(
|
||||
public app: express.Application,
|
||||
public ctx: AppContext,
|
||||
) {}
|
||||
) {
|
||||
this.metricsRegistry = createPrometheusRegistry(ctx)
|
||||
}
|
||||
|
||||
static async create(cfg: Config): Promise<LinkService> {
|
||||
let app = express()
|
||||
@@ -41,11 +48,26 @@ export class LinkService {
|
||||
this.server.keepAliveTimeout = 90000
|
||||
this.terminator = createHttpTerminator({server: this.server})
|
||||
await events.once(this.server, 'listening')
|
||||
|
||||
const metricsApp = express()
|
||||
metricsApp.get('/metrics', (_req, res, next) => {
|
||||
res.set('Content-Type', this.metricsRegistry.contentType)
|
||||
this.metricsRegistry.metrics().then(metrics => res.end(metrics), next)
|
||||
})
|
||||
this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort)
|
||||
this.metricsTerminator = createHttpTerminator({
|
||||
server: this.metricsServer,
|
||||
gracefulTerminationTimeout: 2000,
|
||||
})
|
||||
await events.once(this.metricsServer, 'listening')
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
this.ctx.abortController.abort()
|
||||
await this.terminator?.terminate()
|
||||
await Promise.all([
|
||||
this.terminator?.terminate(),
|
||||
this.metricsTerminator?.terminate(),
|
||||
])
|
||||
await this.ctx.db.close()
|
||||
this.ctx.metrics.stop()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {collectDefaultMetrics, Gauge, Registry} from 'prom-client'
|
||||
|
||||
import {type AppContext} from './context.js'
|
||||
|
||||
let runtimeRegistry: Registry | undefined
|
||||
|
||||
const getRuntimeRegistry = (): Registry => {
|
||||
if (!runtimeRegistry) {
|
||||
runtimeRegistry = new Registry()
|
||||
|
||||
// Beyla already exports HTTP RED metrics and traces for Blink. These
|
||||
// process metrics cover the runtime-only failure modes it cannot see,
|
||||
// particularly event-loop stalls, GC pauses, and V8 heap pressure.
|
||||
collectDefaultMetrics({register: runtimeRegistry})
|
||||
}
|
||||
|
||||
return runtimeRegistry
|
||||
}
|
||||
|
||||
export const createPrometheusRegistry = (ctx: AppContext): Registry => {
|
||||
const poolRegistry = new Registry()
|
||||
|
||||
new Gauge<'state'>({
|
||||
name: 'bskylink_db_pool_connections',
|
||||
help: 'PostgreSQL client connections by usage state.',
|
||||
labelNames: ['state'],
|
||||
registers: [poolRegistry],
|
||||
collect() {
|
||||
const {idleCount, totalCount} = ctx.db.cfg.pool
|
||||
this.set({state: 'idle'}, idleCount)
|
||||
this.set({state: 'in_use'}, totalCount - idleCount)
|
||||
},
|
||||
})
|
||||
|
||||
new Gauge({
|
||||
name: 'bskylink_db_pool_max_connections',
|
||||
help: 'Configured maximum PostgreSQL client connections.',
|
||||
registers: [poolRegistry],
|
||||
collect() {
|
||||
this.set(ctx.cfg.db.pool.size)
|
||||
},
|
||||
})
|
||||
|
||||
new Gauge({
|
||||
name: 'bskylink_db_pool_waiting_requests',
|
||||
help: 'Requests waiting for a PostgreSQL client connection.',
|
||||
registers: [poolRegistry],
|
||||
collect() {
|
||||
this.set(ctx.db.cfg.pool.waitingCount)
|
||||
},
|
||||
})
|
||||
|
||||
return Registry.merge([getRuntimeRegistry(), poolRegistry])
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {linkRedirectContents} from '../html/linkRedirectContents.js'
|
||||
import {linkWarningContents} from '../html/linkWarningContents.js'
|
||||
import {linkWarningLayout} from '../html/linkWarningLayout.js'
|
||||
import {redirectLogger} from '../logger.js'
|
||||
import {handler} from './util.js'
|
||||
import {observedHandler} from './util.js'
|
||||
|
||||
const INTERNAL_IP_REGEX = new RegExp(
|
||||
'(^127.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$)|(^10.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$)|(^172.1[6-9]{1}[0-9]{0,1}.[0-9]{1,3}.[0-9]{1,3}$)|(^172.2[0-9]{1}[0-9]{0,1}.[0-9]{1,3}.[0-9]{1,3}$)|(^172.3[0-1]{1}[0-9]{0,1}.[0-9]{1,3}.[0-9]{1,3}$)|(^192.168.[0-9]{1,3}.[0-9]{1,3}$)|^localhost',
|
||||
@@ -18,7 +18,7 @@ const INTERNAL_IP_REGEX = new RegExp(
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get(
|
||||
'/redirect',
|
||||
handler(async (req, res) => {
|
||||
observedHandler('redirect', async (req, res) => {
|
||||
let link = req.query.u
|
||||
assert(
|
||||
typeof link === 'string',
|
||||
|
||||
@@ -4,23 +4,25 @@ import {DAY, SECOND} from '@atproto/common'
|
||||
import {Express} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {handler} from './util.js'
|
||||
import {observedHandler} from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get(
|
||||
'/:linkId',
|
||||
handler(async (req, res) => {
|
||||
observedHandler('short_link', async (req, res) => {
|
||||
const linkId = req.params.linkId
|
||||
const contentType = req.accepts(['html', 'json'])
|
||||
assert(
|
||||
typeof linkId === 'string',
|
||||
'express guarantees id parameter is a string',
|
||||
)
|
||||
const found = await ctx.db.db
|
||||
.selectFrom('link')
|
||||
.selectAll()
|
||||
.where('id', '=', linkId)
|
||||
.executeTakeFirst()
|
||||
const found = await ctx.db.observeQuery('resolve_short_link', () =>
|
||||
ctx.db.db
|
||||
.selectFrom('link')
|
||||
.selectAll()
|
||||
.where('id', '=', linkId)
|
||||
.executeTakeFirst(),
|
||||
)
|
||||
if (!found) {
|
||||
// potentially broken or mistyped link
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import {performance} from 'node:perf_hooks'
|
||||
|
||||
import {ErrorRequestHandler, Request, RequestHandler, Response} from 'express'
|
||||
|
||||
import {httpLogger} from '../logger.js'
|
||||
|
||||
const SLOW_REQUEST_THRESHOLD_MS = 1000
|
||||
|
||||
export type Handler = (req: Request, res: Response) => Awaited<void>
|
||||
|
||||
export const handler = (runHandler: Handler): RequestHandler => {
|
||||
@@ -14,6 +18,32 @@ export const handler = (runHandler: Handler): RequestHandler => {
|
||||
}
|
||||
}
|
||||
|
||||
export const observedHandler = (
|
||||
operation: string,
|
||||
runHandler: Handler,
|
||||
): RequestHandler => {
|
||||
return handler(async (req, res) => {
|
||||
const startedAt = performance.now()
|
||||
try {
|
||||
await runHandler(req, res)
|
||||
} finally {
|
||||
const durationMs = Math.round(performance.now() - startedAt)
|
||||
if (durationMs >= SLOW_REQUEST_THRESHOLD_MS) {
|
||||
httpLogger.warn(
|
||||
{
|
||||
durationMs,
|
||||
method: req.method,
|
||||
operation,
|
||||
requestTraceId: req.get('x-amzn-trace-id'),
|
||||
statusCode: res.statusCode,
|
||||
},
|
||||
'slow request',
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const errorHandler: ErrorRequestHandler = (err, _req, res, next) => {
|
||||
httpLogger.error({err}, 'request error')
|
||||
if (res.headersSent) {
|
||||
|
||||
@@ -161,6 +161,11 @@
|
||||
dependencies:
|
||||
make-plural "^7.0.0"
|
||||
|
||||
"@opentelemetry/api@^1.4.0":
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.1.tgz#c1b0346de336ba55af2d5a7970882037baedec05"
|
||||
integrity sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2"
|
||||
@@ -338,6 +343,11 @@ base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
bintrees@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8"
|
||||
integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==
|
||||
|
||||
body-parser@1.20.2, body-parser@^1.20.2:
|
||||
version "1.20.2"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
|
||||
@@ -1060,6 +1070,14 @@ process@^0.11.10:
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||
|
||||
prom-client@^15.1.3:
|
||||
version "15.1.3"
|
||||
resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2"
|
||||
integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==
|
||||
dependencies:
|
||||
"@opentelemetry/api" "^1.4.0"
|
||||
tdigest "^0.1.1"
|
||||
|
||||
proxy-addr@~2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
|
||||
@@ -1232,6 +1250,13 @@ string_decoder@^1.3.0:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
tdigest@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced"
|
||||
integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==
|
||||
dependencies:
|
||||
bintrees "1.0.2"
|
||||
|
||||
thread-stream@^2.6.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"devEngines": {
|
||||
"packageManager": {
|
||||
"name": "pnpm",
|
||||
"version": "11.13.1",
|
||||
"version": "11.21.0",
|
||||
"onFail": "warn"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,52 +7,52 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
'@pnpm/exe':
|
||||
specifier: 11.13.1
|
||||
version: 11.13.1
|
||||
specifier: 11.21.0
|
||||
version: 11.21.0
|
||||
pnpm:
|
||||
specifier: 11.13.1
|
||||
version: 11.13.1
|
||||
specifier: 11.21.0
|
||||
version: 11.21.0
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe@11.13.1':
|
||||
resolution: {integrity: sha512-P4euEK6lOFnd5oTHEc5M/HhvyF4XUhTnVsklEcM6rmY0QJxPD6xbT+u1+gskEIBp4nSRorz20IJQtAU1Nerggg==}
|
||||
'@pnpm/exe@11.21.0':
|
||||
resolution: {integrity: sha512-zawQxIewH1od72HhlmXWq3No6XyuWn+nMvQ9BjWWGNBVskmS+RDlTu7ey2ruL650PbbyuCATYSal1DaXFKBdcw==}
|
||||
hasBin: true
|
||||
|
||||
'@pnpm/linux-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-wB8zloqrYrudPyuA5qbuTCnJGe4eETPwqOjoPjoyyyvA4zFI5XfLpxgqOOcaY5UJBoqzckcGpRVDwhSRfsQ/6A==}
|
||||
'@pnpm/linux-arm64@11.21.0':
|
||||
resolution: {integrity: sha512-gOSfQKr6kZjEwHyoRwMt9qrqQ9sqbZmUm2hbgJJG8bp0ZR9YkQ4BZV2k4qlQA2jtsmHV1u1MwiaLcuK7DauvBg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@pnpm/linux-x64@11.13.1':
|
||||
resolution: {integrity: sha512-A+wnEvzfWEvanXiwww3tnOPmtjPSrrf5tOP6vk8+K0BRFEe/Df0oPytm2nWgGcn5iwPnqtr1Btkof913McnSPA==}
|
||||
'@pnpm/linux-x64@11.21.0':
|
||||
resolution: {integrity: sha512-X+kBR8yscKyhhElO+WLrb6sFbl/3Ow70B+6fqZUYI8T8wtmlCw5GtcPXVBJPDJcLN5joe227h7lyCCZo4tdKdw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@pnpm/linuxstatic-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-k4t65VeqRX4COMFe45TF58CVmCpmAsKZShaR1HobmUeleo98mWTctggKolrA2MHcVUeSS+12yB5Urb3uDazhmw==}
|
||||
'@pnpm/linuxstatic-arm64@11.21.0':
|
||||
resolution: {integrity: sha512-IUJfAclH0b3QxaHuQuVxXQIzEkDtTm0C+G3tgG0ET5tDRGc7wH7eU0GEM75ojHOwzqv7s0y00xPVVCIsxUM4Nw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/linuxstatic-x64@11.13.1':
|
||||
resolution: {integrity: sha512-A65GqPzwCl0bAMk3kRWfbjSRBm5RRaqR2oMxV/9AYZrwO0X9yEfngbLBISCPHjt6/Qe4nH7DFemyhy6yODYwEw==}
|
||||
'@pnpm/linuxstatic-x64@11.21.0':
|
||||
resolution: {integrity: sha512-6Y2u+AfOUuTqWgTCpFhySL8HAcONDucCFixKle9tWoW7bm8RF0+fwQBRWNWGdx+Toau07wZ1LNZPqN8gpgeBDQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/macos-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-MJvOtyGOWSfBoqdVEfAH8ljmHs13mt82k/UxN4f+q7koDxJRR2n4Nie6Og6RwbnbaubCz0Fh2bTeL1+MxDSFpA==}
|
||||
'@pnpm/macos-arm64@11.21.0':
|
||||
resolution: {integrity: sha512-sLMGvVJXWdhFAouY2icjeZ2VCFmyPPZvvtHkfj1oeCWGppsbWcP5cExSw9yU1Uw7ALwrV0I2lRZv33yWYfDtcQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/win-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-kl/g1cCKOJPe4HntspyrAJW0LRco0UHnVfxHSspezo4Zj4AanJAZ8WzLqfa6/w3lBSKHTEN4x0pb3m4J7B7Vpw==}
|
||||
'@pnpm/win-arm64@11.21.0':
|
||||
resolution: {integrity: sha512-79Nc+YI5B2ddH5MQD2YITL/PKnmXdcQKwmwx0HaD4QnsCco8DFaTho242e5sd9QfXKxYRvB9AOnuqIV4VjhAAw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/win-x64@11.13.1':
|
||||
resolution: {integrity: sha512-Bcb14NeBlbHS2Gq1qr8VnCiAz5eC1lYzXOls7zH0bnV0Taaj4/xyfm0HVO4dn9R2TQtVtz1qnBZHHL9PDFumqQ==}
|
||||
'@pnpm/win-x64@11.21.0':
|
||||
resolution: {integrity: sha512-zT3TufmVOroWPrzXTPPPgYvIsTZIsK13kjpmgXlICyEFrhd16RFLoTWFhP+8UqHXpTNmVbtTHzg+fEVYy9rlEQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -116,45 +116,45 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pnpm@11.13.1:
|
||||
resolution: {integrity: sha512-svx2g7imUlQU59E+G6KMqt3elr9m7FQL+ut+cCuB8+C+TR8pXt9/n+A5Z0Co3ORQnFgt33mJH0VD/qMtN2RfJQ==}
|
||||
pnpm@11.21.0:
|
||||
resolution: {integrity: sha512-UhcFvOaJkk6scvWjWHEi82JonvZXHlW6gAdv1jfBETLs/62ib61Op5xIW/3b/T1aKlsFgFp36JPeceyKbMo7sQ==}
|
||||
engines: {node: '>=22.13'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe@11.13.1':
|
||||
'@pnpm/exe@11.21.0':
|
||||
dependencies:
|
||||
'@reflink/reflink': 0.1.19
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
'@pnpm/linux-arm64': 11.13.1
|
||||
'@pnpm/linux-x64': 11.13.1
|
||||
'@pnpm/linuxstatic-arm64': 11.13.1
|
||||
'@pnpm/linuxstatic-x64': 11.13.1
|
||||
'@pnpm/macos-arm64': 11.13.1
|
||||
'@pnpm/win-arm64': 11.13.1
|
||||
'@pnpm/win-x64': 11.13.1
|
||||
'@pnpm/linux-arm64': 11.21.0
|
||||
'@pnpm/linux-x64': 11.21.0
|
||||
'@pnpm/linuxstatic-arm64': 11.21.0
|
||||
'@pnpm/linuxstatic-x64': 11.21.0
|
||||
'@pnpm/macos-arm64': 11.21.0
|
||||
'@pnpm/win-arm64': 11.21.0
|
||||
'@pnpm/win-x64': 11.21.0
|
||||
|
||||
'@pnpm/linux-arm64@11.13.1':
|
||||
'@pnpm/linux-arm64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linux-x64@11.13.1':
|
||||
'@pnpm/linux-x64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linuxstatic-arm64@11.13.1':
|
||||
'@pnpm/linuxstatic-arm64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linuxstatic-x64@11.13.1':
|
||||
'@pnpm/linuxstatic-x64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/macos-arm64@11.13.1':
|
||||
'@pnpm/macos-arm64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/win-arm64@11.13.1':
|
||||
'@pnpm/win-arm64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/win-x64@11.13.1':
|
||||
'@pnpm/win-x64@11.21.0':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-darwin-arm64@0.1.19':
|
||||
@@ -194,7 +194,7 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
pnpm@11.13.1: {}
|
||||
pnpm@11.21.0: {}
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
(c) 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'.
|
||||
Copyright 2015-2020 Google LLC. All Rights Reserved.
|
||||
Copyright 2024 The Noto Project Authors (https://github.com/notofonts/hebrew)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/thai)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Font licensing
|
||||
|
||||
This directory contains Inter subsets tracked in the repository and Noto Sans fonts downloaded during the OG card build. Both are separately licensed under the SIL Open Font License, Version 1.1, rather than Bluesky's [MIT license](../../../../LICENSE).
|
||||
|
||||
## Inter
|
||||
|
||||
```
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
```
|
||||
|
||||
The tracked `Inter-Bold.ttf`, `Inter-Regular.ttf`, and `Inter-SemiBold.ttf` files are subsets of [Inter](https://rsms.me/inter/) by Rasmus Andersson. The full license text is at [`assets/fonts/inter/OFL.txt`](../../../../assets/fonts/inter/OFL.txt). The bundled OFL does not designate a Reserved Font Name.
|
||||
|
||||
## Noto Sans
|
||||
|
||||
[`bskyogcard/scripts/install-fonts.ts`](../../../scripts/install-fonts.ts) downloads Noto Sans Arabic, Hebrew, HK, JP, KR, SC, TC, and Thai into this directory. The build copies them into `bskyogcard/dist/assets/fonts/`, and the Docker image contains both locations.
|
||||
|
||||
```
|
||||
(c) 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'.
|
||||
Copyright 2015-2020 Google LLC. All Rights Reserved.
|
||||
Copyright 2024 The Noto Project Authors (https://github.com/notofonts/hebrew)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/thai)
|
||||
```
|
||||
|
||||
Their full license text is in [`OFL-NOTO.txt`](./OFL-NOTO.txt). The CJK families reserve the name "Source."
|
||||
|
||||
**You may redistribute these fonts under their respective OFL notices. Keep the applicable copyright notice and OFL text with every redistributed copy.**
|
||||
@@ -7,5 +7,4 @@ export const isView = AppBskyGraphDefs.isStarterPackView
|
||||
* Matches any starter pack view exported by our SDK
|
||||
*/
|
||||
export type AnyStarterPackView =
|
||||
| AppBskyGraphDefs.StarterPackViewBasic
|
||||
| AppBskyGraphDefs.StarterPackView
|
||||
AppBskyGraphDefs.StarterPackViewBasic | AppBskyGraphDefs.StarterPackView
|
||||
|
||||
@@ -554,11 +554,8 @@ func buildPostNode(pv *appbsky.FeedDefs_PostView, replies []*appbsky.FeedDefs_Th
|
||||
}
|
||||
}
|
||||
|
||||
if pv.ReplyCount != nil {
|
||||
if pv.ReplyCount != nil && *pv.ReplyCount > 0 {
|
||||
node.CommentCount = pv.ReplyCount
|
||||
} else {
|
||||
zero := int64(0)
|
||||
node.CommentCount = &zero
|
||||
}
|
||||
|
||||
if !embedHidden {
|
||||
|
||||
@@ -313,7 +313,7 @@ func TestBuildPostJSONLD_Bare(t *testing.T) {
|
||||
if main["datePublished"] != "2024-01-02T03:04:05Z" {
|
||||
t.Errorf("datePublished wrong: %v", main["datePublished"])
|
||||
}
|
||||
// commentCount should always be emitted, even at zero.
|
||||
// Positive commentCount values should be emitted.
|
||||
cc, ok := main["commentCount"].(float64)
|
||||
if !ok || int64(cc) != 3 {
|
||||
t.Errorf("commentCount wrong: %v", main["commentCount"])
|
||||
@@ -342,6 +342,41 @@ func TestBuildPostJSONLD_Bare(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPostJSONLD_CommentCount(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
count *int64
|
||||
want *int64
|
||||
}{
|
||||
{name: "nil", count: nil, want: nil},
|
||||
{name: "zero", count: intPtr(0), want: nil},
|
||||
{name: "negative", count: intPtr(-1), want: nil},
|
||||
{name: "positive", count: intPtr(3), want: intPtr(3)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pv := makePostView("alice.bsky.social", "did:plc:alice", "abc123", "hello")
|
||||
pv.ReplyCount = tt.count
|
||||
out, err := buildPostJSONLD(pv, nil, "u", "", hideEmbedLabels, hideReplyLabels)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
main := unmarshalLD(t, out)["mainEntity"].(map[string]any)
|
||||
got, present := main["commentCount"]
|
||||
if tt.want == nil {
|
||||
if present {
|
||||
t.Errorf("commentCount should be omitted, got %v", got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !present || int64(got.(float64)) != *tt.want {
|
||||
t.Errorf("commentCount = %v, want %d", got, *tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPostJSONLD_WithImages(t *testing.T) {
|
||||
thumb1 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/abc@jpeg"
|
||||
thumb2 := "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:alice/def@jpeg"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# codes are used for rate-limiting. Up to a handful concurrent requests should
|
||||
# be ok.
|
||||
User-Agent: *
|
||||
Disallow: /intent/compose
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://bsky.app/sitemap/users.xml.gz
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Important: when a two letter code is ambiguous, we need
|
||||
# to manually map it to the full locale code. This is done
|
||||
# in the Crowdin language mapping config here:
|
||||
# https://bluesky.crowdin.com/u/projects/1/languages
|
||||
{
|
||||
"project_id": "1",
|
||||
"base_url": "https://bluesky.api.crowdin.com",
|
||||
@@ -6,19 +10,7 @@
|
||||
"files": [
|
||||
{
|
||||
"source": "/src/locale/locales/en/messages.po",
|
||||
"translation": "/src/locale/locales/%two_letters_code%/messages.po",
|
||||
"languages_mapping": {
|
||||
"two_letters_code": {
|
||||
"pt": "pt-PT",
|
||||
"pt-BR": "pt-BR",
|
||||
"en-CA": "en-CA",
|
||||
"en-GB": "en-GB",
|
||||
"fr-CA": "fr-CA",
|
||||
"zh-CN": "zh-CN",
|
||||
"zh-TW": "zh-TW",
|
||||
"zh-HK": "zh-HK"
|
||||
}
|
||||
}
|
||||
"translation": "/src/locale/locales/%two_letters_code%/messages.po"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ Double check yourself by ensuring that the `version` in `package.json` matches
|
||||
what's in the latest release.
|
||||
|
||||
This new OTA branch should follow the format `1.X.0-ota-1`. If one or more OTAs
|
||||
have already been deployed for this release, incremement the branch name e.g.
|
||||
have already been deployed for this release, increment the branch name e.g.
|
||||
`1.x.0-ota-2`.
|
||||
|
||||
### 4. Add commits to the OTA branch
|
||||
@@ -63,30 +63,7 @@ Run this and commit the result as the last commit on the OTA branch.
|
||||
pnpm intl:release
|
||||
```
|
||||
|
||||
### 6. Manually set build numbers
|
||||
|
||||
Log in to the EAS CLI with `eas login` and manually set the build numbers to the
|
||||
values you found in **Step 1**.
|
||||
|
||||
> [!NOTE]
|
||||
> If you don’t already have the EAS CLI, you can install it with `pnpm add -g eas-cli`.
|
||||
|
||||
```sh
|
||||
> npx eas build:version:set -p ios
|
||||
>
|
||||
> Project @blueskysocial/bluesky with bundle identifier "xyz.blueskyweb.app" is configured with buildNumber 1011.
|
||||
> ✔ What version would you like to set? … 1009
|
||||
>
|
||||
> npx eas build:version:set -p android
|
||||
>
|
||||
> Project @blueskysocial/bluesky with application ID "xyz.blueskyweb.app" is configured with versionCode 641.
|
||||
> ✔ What version would you like to set? … 639
|
||||
```
|
||||
|
||||
👉 **Save the previous values,** in this case `1011` and `641`, so you can reset
|
||||
them after the OTA completes.
|
||||
|
||||
### 7. Run the GitHub actions
|
||||
### 6. Run the GitHub actions
|
||||
You'll need to run two separate actions: one to deploy the iOS/Android OTA
|
||||
itself, and one to build the web Docker container.
|
||||
|
||||
@@ -96,14 +73,20 @@ and run the action.
|
||||
|
||||
| Steps | |
|
||||
| ----- | --- |
|
||||
| Select your OTA branch `1.x.0-ota-x`, select `production` in the dropdown, enter the git tag of the latest release `1.x.0`, and click "Run workflow" |  |
|
||||
| Select your OTA branch `1.x.0-ota-x`, select `production` in the dropdown, enter the git tag of the latest release `1.x.0`, enter the iOS build number and Android version code you found in **Step 1**, and click "Run workflow" |  |
|
||||
|
||||
> [!NOTE]
|
||||
> Production OTAs are bound to the specific native build they target, so the
|
||||
> workflow requires the build numbers to be entered manually. There is no need
|
||||
> to change the global EAS build counters (and doing so is no longer necessary
|
||||
> for OTAs - they are only used when producing new native builds).
|
||||
|
||||
> [!NOTE]
|
||||
> If you do enter an incorrect version here, the deployment will either:
|
||||
> - Fail, because the action cannot find a commit with your misentered version
|
||||
> - Succeed, but with no users receiving the update. This is because the version
|
||||
> you entered will not properly correlate to a _build number_ as well, so no
|
||||
> clients in the wild will be able to receive the update.
|
||||
> - Succeed, but with no users receiving the update. This is because the
|
||||
> version and build numbers you entered will not match any clients in the
|
||||
> wild, so none will be able to receive the update.
|
||||
|
||||
**For web,** head to [Actions >
|
||||
build-and-push-bskyweb-aws](https://github.com/bluesky-social/social-app/actions/workflows/build-and-push-bskyweb-aws.yaml)
|
||||
@@ -113,13 +96,13 @@ and run the action.
|
||||
| ----- | --- |
|
||||
| Select your OTA branch `1.x.0-ota-x` and click "Run workflow" |  |
|
||||
|
||||
### 8. Deploy web
|
||||
### 7. Deploy web
|
||||
|
||||
Once the web Docker container build finishes, go to your `1.x.0-ota-x` branch,
|
||||
copy the most recent commit hash. Post this hash in `#ops-deploys` and request
|
||||
someone with web deploy access deploy the built container.
|
||||
|
||||
### 9. Confirm successful deployment
|
||||
### 8. Confirm successful deployment
|
||||
|
||||
In about five minutes, the new deployment should be deployed and devices will
|
||||
begin downloading and installing in the background.
|
||||
@@ -130,23 +113,6 @@ build from your device and re-install from the App Store. Then, you'll need to:
|
||||
- Quit and reopen the app
|
||||
- Check the `Settings > About` page and confirm the hash matches the most recent hash on your OTA branch
|
||||
|
||||
### 10. Reset build numbers
|
||||
|
||||
Grab the build numbers you saved in **Step 5** and reverse the EAS CLI commands
|
||||
to reset the build numbers.
|
||||
|
||||
```sh
|
||||
> npx eas build:version:set -p ios
|
||||
>
|
||||
> Project @blueskysocial/bluesky with bundle identifier "xyz.blueskyweb.app" is configured with buildNumber 1009.
|
||||
> ✔ What version would you like to set? … 1011
|
||||
>
|
||||
> npx eas build:version:set -p android
|
||||
>
|
||||
> Project @blueskysocial/bluesky with application ID "xyz.blueskyweb.app" is configured with versionCode 639.
|
||||
> ✔ What version would you like to set? … 641
|
||||
```
|
||||
|
||||
## Overview diagram
|
||||
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 50 KiB |
@@ -49,14 +49,26 @@ Every night, a GitHub action will run `pnpm intl:extract` to update the english
|
||||
1. Pull main and create a branch.
|
||||
1. Run `pnpm intl:release` to fetch all translation updates from Crowdin and extract all `.po` files so that they're synced with the latest code. Commit that.
|
||||
1. Create a PR, ensure the translations all look correct, and merge.
|
||||
1. If needed:
|
||||
1. Merge all approved translation PRs (contributions from outside crowdin).
|
||||
1. Run `pnpm intl:push` to sync Crowdin with the state of the repo.
|
||||
1. If needed, merge all approved translation PRs (contributions from outside crowdin).
|
||||
|
||||
### Testing the translations in Crowdin
|
||||
|
||||
You can run `pnpm intl:pull` to pull the currently-approved translations from Crowdin.
|
||||
|
||||
### Pushing translations to Crowdin
|
||||
|
||||
Run `pnpm intl:push-sources` to push the current state of the english source file to Crowdin. Extract the latest strings first!
|
||||
|
||||
If necessary, you can also push translations to Crowdin manually using `pnpm intl:dangerously-push-translations`. **DO NOT DO THIS WITHOUT A GOOD REASON** - it overrides the translator's work and is very confusing. You're probably better off tweaking strings in Crowdin itself.
|
||||
|
||||
### Adding new languages
|
||||
|
||||
When a new language is added to Crowdin, it gets synced down the next time `pnpm intl:pull` is run. You then need to add the language in a few places - the AppLanguages array, `date-fns`, Intl polyfills etc.
|
||||
|
||||
Importantly, we use a two-letter language code (e.g. `en`, `fr`). Crowdin uses a full locale (e.g. `pt-BR`) and maps them to the two-letter code. This can become ambiguous if we have multiple languages that share a two-letter code (e.g. `fr-FR` and `fr-CA`), so we need to manually override the two-letter code into an unambiguous locale. This is done in the Crowdin language settings (*not* the `crowdin.yml` file!). When a new ambiguous language is added, go to https://bluesky.crowdin.com/u/projects/1/languages and map the language's two-letter code to it's locale. If not, the two languages may silently overwrite each other.
|
||||
|
||||

|
||||
|
||||
## Developers
|
||||
|
||||
### Adding new strings
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"build": {
|
||||
"base": {
|
||||
"node": "24.18.0"
|
||||
"node": "24.19.0"
|
||||
},
|
||||
"development": {
|
||||
"extends": "base",
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
const BANNED_IMPORTS = [
|
||||
'@fortawesome/free-regular-svg-icons',
|
||||
'@fortawesome/free-solid-svg-icons',
|
||||
]
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Prevent importing entire icon packages',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
const source = node.source
|
||||
if (typeof source.value !== 'string') {
|
||||
return
|
||||
}
|
||||
if (BANNED_IMPORTS.includes(source.value)) {
|
||||
context.report({
|
||||
node,
|
||||
message:
|
||||
'Import the specific thing you want instead of the entire package',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -21,9 +21,10 @@ jest.mock('react-native-safe-area-context', () => {
|
||||
const inset = {top: 0, right: 0, bottom: 0, left: 0}
|
||||
return {
|
||||
SafeAreaProvider: jest.fn().mockImplementation(({children}) => children),
|
||||
SafeAreaConsumer: jest
|
||||
.fn()
|
||||
.mockImplementation(({children}) => children(inset)),
|
||||
SafeAreaConsumer: jest.fn().mockImplementation(
|
||||
/** @param {{children: (i: typeof inset) => unknown}} props */
|
||||
({children}) => children(inset),
|
||||
),
|
||||
useSafeAreaInsets: jest.fn().mockImplementation(() => inset),
|
||||
}
|
||||
})
|
||||
@@ -85,18 +86,26 @@ jest.mock('expo-application', () => ({
|
||||
}))
|
||||
|
||||
jest.mock('expo-modules-core', () => ({
|
||||
requireNativeModule: jest.fn().mockImplementation(moduleName => {
|
||||
if (moduleName === 'ExpoPlatformInfo') {
|
||||
return {
|
||||
getIsReducedMotionEnabled: () => false,
|
||||
requireNativeModule: jest.fn().mockImplementation(
|
||||
/** @param {string} moduleName */
|
||||
moduleName => {
|
||||
if (moduleName === 'ExpoPlatformInfo') {
|
||||
return {
|
||||
getIsReducedMotionEnabled: () => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (moduleName === 'BottomSheet') {
|
||||
return {
|
||||
dismissAll: () => {},
|
||||
if (moduleName === 'BottomSheet') {
|
||||
return {
|
||||
dismissAll: () => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
const expoModules = /** @type {Record<string, unknown> | undefined} */ (
|
||||
globalThis.expo?.modules
|
||||
)
|
||||
return expoModules?.[moduleName]
|
||||
},
|
||||
),
|
||||
requireNativeViewManager: jest.fn().mockImplementation(_ => {
|
||||
return () => null
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "app.bsky.actor.contentVisibilityDeclaration",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "record",
|
||||
"description": "A declaration of an account's preferences for appearing in content discovery surfaces.",
|
||||
"key": "literal:self",
|
||||
"record": {
|
||||
"type": "object",
|
||||
"required": ["hideFromAlgorithmicRecommendations"],
|
||||
"properties": {
|
||||
"hideFromAlgorithmicRecommendations": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the account requests that its posts be hidden from algorithmic recommendations. Consumers must treat a missing record as false."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,994 @@
|
||||
{
|
||||
"id": "app.bsky.actor.defs",
|
||||
"defs": {
|
||||
"nux": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"completed"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"maxLength": 3000,
|
||||
"description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.",
|
||||
"maxGraphemes": 300
|
||||
},
|
||||
"completed": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "The date and time at which the NUX will expire and should be considered completed."
|
||||
}
|
||||
},
|
||||
"description": "A new user experiences (NUX) storage object"
|
||||
},
|
||||
"mutedWord": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"value",
|
||||
"targets"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"maxLength": 10000,
|
||||
"description": "The muted word itself.",
|
||||
"maxGraphemes": 1000
|
||||
},
|
||||
"targets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#mutedWordTarget",
|
||||
"type": "ref"
|
||||
},
|
||||
"description": "The intended targets of the muted word."
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "The date and time at which the muted word will expire and no longer be applied."
|
||||
},
|
||||
"actorTarget": {
|
||||
"type": "string",
|
||||
"default": "all",
|
||||
"description": "Groups of users to apply the muted word to. If undefined, applies to all users.",
|
||||
"knownValues": [
|
||||
"all",
|
||||
"exclude-following"
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "A word that the account owner has muted."
|
||||
},
|
||||
"savedFeed": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"type",
|
||||
"value",
|
||||
"pinned"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"feed",
|
||||
"list",
|
||||
"timeline"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"pinned": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"statusView": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"status",
|
||||
"record"
|
||||
],
|
||||
"properties": {
|
||||
"cid": {
|
||||
"type": "string",
|
||||
"format": "cid"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
},
|
||||
"embed": {
|
||||
"refs": [
|
||||
"app.bsky.embed.external#view"
|
||||
],
|
||||
"type": "union",
|
||||
"description": "An optional embed associated with the status."
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "com.atproto.label.defs#label",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
"type": "unknown"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "The status for the account.",
|
||||
"knownValues": [
|
||||
"app.bsky.actor.status#live"
|
||||
]
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean",
|
||||
"description": "True if the status is not expired, false if it is expired. Only present if expiration was set."
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "The date when this status will expire. The application might choose to no longer return the status after expiration."
|
||||
},
|
||||
"isDisabled": {
|
||||
"type": "boolean",
|
||||
"description": "True if the user's go-live access has been disabled by a moderator, false otherwise."
|
||||
}
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"refs": [
|
||||
"#adultContentPref",
|
||||
"#contentLabelPref",
|
||||
"#savedFeedsPref",
|
||||
"#savedFeedsPrefV2",
|
||||
"#personalDetailsPref",
|
||||
"#declaredAgePref",
|
||||
"#feedViewPref",
|
||||
"#threadViewPref",
|
||||
"#interestsPref",
|
||||
"#mutedWordsPref",
|
||||
"#hiddenPostsPref",
|
||||
"#bskyAppStatePref",
|
||||
"#labelersPref",
|
||||
"#postInteractionSettingsPref",
|
||||
"#verificationPrefs",
|
||||
"#liveEventPreferences"
|
||||
],
|
||||
"type": "union"
|
||||
}
|
||||
},
|
||||
"profileView": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"did",
|
||||
"handle"
|
||||
],
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
},
|
||||
"debug": {
|
||||
"type": "unknown",
|
||||
"description": "Debug information for internal development"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"handle": {
|
||||
"type": "string",
|
||||
"format": "handle"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "com.atproto.label.defs#label",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"ref": "#statusView",
|
||||
"type": "ref"
|
||||
},
|
||||
"viewer": {
|
||||
"ref": "#viewerState",
|
||||
"type": "ref"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"indexedAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"associated": {
|
||||
"ref": "#profileAssociated",
|
||||
"type": "ref"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 2560,
|
||||
"maxGraphemes": 256
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"verification": {
|
||||
"ref": "#verificationState",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewerState": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"muted": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the account is fully muted, directly or via a mutelist. False when the mute is scoped to specific kinds; see mutedOnlyReposts and mutedOnlyQuoteposts."
|
||||
},
|
||||
"blocking": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
},
|
||||
"blockedBy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"following": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
},
|
||||
"followedBy": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
},
|
||||
"mutedByList": {
|
||||
"ref": "app.bsky.graph.defs#listViewBasic",
|
||||
"type": "ref"
|
||||
},
|
||||
"blockingByList": {
|
||||
"ref": "app.bsky.graph.defs#listViewBasic",
|
||||
"type": "ref"
|
||||
},
|
||||
"knownFollowers": {
|
||||
"ref": "#knownFollowers",
|
||||
"type": "ref",
|
||||
"description": "This property is present only in selected cases, as an optimization."
|
||||
},
|
||||
"mutedOnlyReposts": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the account's reposts are muted. Scoped mutes are exclusive with muted: this can be true while muted is false. If muted is true, this will be false."
|
||||
},
|
||||
"mutedOnlyQuoteposts": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the account's quote posts are muted. Scoped mutes are exclusive with muted: this can be true while muted is false. If muted is true, this will be false."
|
||||
},
|
||||
"activitySubscription": {
|
||||
"ref": "app.bsky.notification.defs#activitySubscription",
|
||||
"type": "ref",
|
||||
"description": "This property is present only in selected cases, as an optimization."
|
||||
}
|
||||
},
|
||||
"description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests."
|
||||
},
|
||||
"feedViewPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"feed"
|
||||
],
|
||||
"properties": {
|
||||
"feed": {
|
||||
"type": "string",
|
||||
"description": "The URI of the feed, or an identifier which describes the feed."
|
||||
},
|
||||
"hideReplies": {
|
||||
"type": "boolean",
|
||||
"description": "Hide replies in the feed."
|
||||
},
|
||||
"hideReposts": {
|
||||
"type": "boolean",
|
||||
"description": "Hide reposts in the feed."
|
||||
},
|
||||
"hideQuotePosts": {
|
||||
"type": "boolean",
|
||||
"description": "Hide quote posts in the feed."
|
||||
},
|
||||
"hideRepliesByLikeCount": {
|
||||
"type": "integer",
|
||||
"description": "Hide replies in the feed if they do not have this number of likes."
|
||||
},
|
||||
"hideRepliesByUnfollowed": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Hide replies in the feed if they are not by followed users."
|
||||
}
|
||||
}
|
||||
},
|
||||
"labelersPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"labelers"
|
||||
],
|
||||
"properties": {
|
||||
"labelers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "#labelerPrefItem",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"interestsPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tags"
|
||||
],
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"maxLength": 100,
|
||||
"description": "A list of tags which describe the account owner's interests gathered during onboarding."
|
||||
}
|
||||
}
|
||||
},
|
||||
"knownFollowers": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"count",
|
||||
"followers"
|
||||
],
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"followers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "#profileViewBasic",
|
||||
"type": "ref"
|
||||
},
|
||||
"maxLength": 5,
|
||||
"minLength": 0
|
||||
}
|
||||
},
|
||||
"description": "The subject's followers whom you also follow"
|
||||
},
|
||||
"mutedWordsPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"items"
|
||||
],
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#mutedWord",
|
||||
"type": "ref"
|
||||
},
|
||||
"description": "A list of words the account owner has muted."
|
||||
}
|
||||
}
|
||||
},
|
||||
"savedFeedsPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pinned",
|
||||
"saved"
|
||||
],
|
||||
"properties": {
|
||||
"saved": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
}
|
||||
},
|
||||
"pinned": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
}
|
||||
},
|
||||
"timelineIndex": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"threadViewPref": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sort": {
|
||||
"type": "string",
|
||||
"description": "Sorting mode for threads.",
|
||||
"knownValues": [
|
||||
"oldest",
|
||||
"newest",
|
||||
"most-likes",
|
||||
"random",
|
||||
"hotness"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"declaredAgePref": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isOverAge13": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates if the user has declared that they are over 13 years of age."
|
||||
},
|
||||
"isOverAge16": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates if the user has declared that they are over 16 years of age."
|
||||
},
|
||||
"isOverAge18": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates if the user has declared that they are over 18 years of age."
|
||||
}
|
||||
},
|
||||
"description": "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration."
|
||||
},
|
||||
"hiddenPostsPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"items"
|
||||
],
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "at-uri"
|
||||
},
|
||||
"description": "A list of URIs of posts the account owner has hidden."
|
||||
}
|
||||
}
|
||||
},
|
||||
"labelerPrefItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"did"
|
||||
],
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mutedWordTarget": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"knownValues": [
|
||||
"content",
|
||||
"tag"
|
||||
],
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"adultContentPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"bskyAppStatePref": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nuxs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#nux",
|
||||
"type": "ref"
|
||||
},
|
||||
"maxLength": 100,
|
||||
"description": "Storage for NUXs the user has encountered."
|
||||
},
|
||||
"isBetaUser": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates if the user is participating in the beta features program."
|
||||
},
|
||||
"queuedNudges": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
},
|
||||
"maxLength": 1000,
|
||||
"description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user."
|
||||
},
|
||||
"activeProgressGuide": {
|
||||
"ref": "#bskyAppProgressGuide",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this."
|
||||
},
|
||||
"contentLabelPref": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"label",
|
||||
"visibility"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"labelerDid": {
|
||||
"type": "string",
|
||||
"format": "did",
|
||||
"description": "Which labeler does this preference apply to? If undefined, applies globally."
|
||||
},
|
||||
"visibility": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"ignore",
|
||||
"show",
|
||||
"warn",
|
||||
"hide"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"profileViewBasic": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"did",
|
||||
"handle"
|
||||
],
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
},
|
||||
"debug": {
|
||||
"type": "unknown",
|
||||
"description": "Debug information for internal development"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"handle": {
|
||||
"type": "string",
|
||||
"format": "handle"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "com.atproto.label.defs#label",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"ref": "#statusView",
|
||||
"type": "ref"
|
||||
},
|
||||
"viewer": {
|
||||
"ref": "#viewerState",
|
||||
"type": "ref"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"associated": {
|
||||
"ref": "#profileAssociated",
|
||||
"type": "ref"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"verification": {
|
||||
"ref": "#verificationState",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"savedFeedsPrefV2": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"items"
|
||||
],
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#savedFeed",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"verificationView": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"issuer",
|
||||
"uri",
|
||||
"isValid",
|
||||
"createdAt"
|
||||
],
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"format": "at-uri",
|
||||
"description": "The AT-URI of the verification record."
|
||||
},
|
||||
"issuer": {
|
||||
"type": "string",
|
||||
"format": "did",
|
||||
"description": "The user who issued this verification."
|
||||
},
|
||||
"isValid": {
|
||||
"type": "boolean",
|
||||
"description": "True if the verification passes validation, otherwise false."
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "Timestamp when the verification was created."
|
||||
},
|
||||
"issuerHandle": {
|
||||
"type": "string",
|
||||
"format": "handle",
|
||||
"description": "The handle of the issuer."
|
||||
},
|
||||
"issuerDisplayName": {
|
||||
"type": "string",
|
||||
"description": "The display name of the issuer."
|
||||
}
|
||||
},
|
||||
"description": "An individual verification for an associated subject."
|
||||
},
|
||||
"profileAssociated": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chat": {
|
||||
"ref": "#profileAssociatedChat",
|
||||
"type": "ref"
|
||||
},
|
||||
"germ": {
|
||||
"ref": "#profileAssociatedGerm",
|
||||
"type": "ref"
|
||||
},
|
||||
"lists": {
|
||||
"type": "integer"
|
||||
},
|
||||
"labeler": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"feedgens": {
|
||||
"type": "integer"
|
||||
},
|
||||
"starterPacks": {
|
||||
"type": "integer"
|
||||
},
|
||||
"activitySubscription": {
|
||||
"ref": "#profileAssociatedActivitySubscription",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verificationPrefs": {
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {
|
||||
"hideBadges": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Hide the blue check badges for verified accounts and trusted verifiers."
|
||||
}
|
||||
},
|
||||
"description": "Preferences for how verified accounts appear in the app."
|
||||
},
|
||||
"verificationState": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verifications",
|
||||
"verifiedStatus",
|
||||
"trustedVerifierStatus"
|
||||
],
|
||||
"properties": {
|
||||
"verifications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "#verificationView",
|
||||
"type": "ref"
|
||||
},
|
||||
"description": "All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included."
|
||||
},
|
||||
"verifiedStatus": {
|
||||
"type": "string",
|
||||
"description": "The user's status as a verified account.",
|
||||
"knownValues": [
|
||||
"valid",
|
||||
"invalid",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"trustedVerifierStatus": {
|
||||
"type": "string",
|
||||
"description": "The user's status as a trusted verifier.",
|
||||
"knownValues": [
|
||||
"valid",
|
||||
"invalid",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Represents the verification information about the user this object is attached to."
|
||||
},
|
||||
"personalDetailsPref": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"birthDate": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"description": "The birth date of account owner."
|
||||
}
|
||||
}
|
||||
},
|
||||
"profileViewDetailed": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"did",
|
||||
"handle"
|
||||
],
|
||||
"properties": {
|
||||
"did": {
|
||||
"type": "string",
|
||||
"format": "did"
|
||||
},
|
||||
"debug": {
|
||||
"type": "unknown",
|
||||
"description": "Debug information for internal development"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"banner": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"handle": {
|
||||
"type": "string",
|
||||
"format": "handle"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "com.atproto.label.defs#label",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"ref": "#statusView",
|
||||
"type": "ref"
|
||||
},
|
||||
"viewer": {
|
||||
"ref": "#viewerState",
|
||||
"type": "ref"
|
||||
},
|
||||
"website": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"indexedAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"associated": {
|
||||
"ref": "#profileAssociated",
|
||||
"type": "ref"
|
||||
},
|
||||
"pinnedPost": {
|
||||
"ref": "com.atproto.repo.strongRef",
|
||||
"type": "ref"
|
||||
},
|
||||
"postsCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 2560,
|
||||
"maxGraphemes": 256
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"followsCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"verification": {
|
||||
"ref": "#verificationState",
|
||||
"type": "ref"
|
||||
},
|
||||
"followersCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"joinedViaStarterPack": {
|
||||
"ref": "app.bsky.graph.defs#starterPackViewBasic",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bskyAppProgressGuide": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"guide"
|
||||
],
|
||||
"properties": {
|
||||
"guide": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
}
|
||||
},
|
||||
"description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress."
|
||||
},
|
||||
"liveEventPreferences": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hideAllFeeds": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to hide all feeds from live events."
|
||||
},
|
||||
"hiddenFeedIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "A list of feed IDs that the user has hidden from live events."
|
||||
}
|
||||
},
|
||||
"description": "Preferences for live events."
|
||||
},
|
||||
"profileAssociatedChat": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"allowIncoming"
|
||||
],
|
||||
"properties": {
|
||||
"allowIncoming": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"all",
|
||||
"none",
|
||||
"following"
|
||||
]
|
||||
},
|
||||
"allowGroupInvites": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"all",
|
||||
"none",
|
||||
"following"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"profileAssociatedGerm": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"showButtonTo",
|
||||
"messageMeUrl"
|
||||
],
|
||||
"properties": {
|
||||
"messageMeUrl": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"showButtonTo": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"usersIFollow",
|
||||
"everyone"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"postInteractionSettingsPref": {
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {
|
||||
"threadgateAllowRules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"refs": [
|
||||
"app.bsky.feed.threadgate#mentionRule",
|
||||
"app.bsky.feed.threadgate#followerRule",
|
||||
"app.bsky.feed.threadgate#followingRule",
|
||||
"app.bsky.feed.threadgate#listRule"
|
||||
],
|
||||
"type": "union"
|
||||
},
|
||||
"maxLength": 5,
|
||||
"description": "Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply."
|
||||
},
|
||||
"postgateEmbeddingRules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"refs": [
|
||||
"app.bsky.feed.postgate#disableRule"
|
||||
],
|
||||
"type": "union"
|
||||
},
|
||||
"maxLength": 5,
|
||||
"description": "Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed."
|
||||
}
|
||||
},
|
||||
"description": "Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly."
|
||||
},
|
||||
"profileAssociatedActivitySubscription": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"allowSubscriptions"
|
||||
],
|
||||
"properties": {
|
||||
"allowSubscriptions": {
|
||||
"type": "string",
|
||||
"knownValues": [
|
||||
"followers",
|
||||
"mutuals",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"id": "app.bsky.actor.getPreferences",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"output": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"preferences"
|
||||
],
|
||||
"properties": {
|
||||
"preferences": {
|
||||
"ref": "app.bsky.actor.defs#preferences",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"properties": {}
|
||||
},
|
||||
"description": "Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"id": "app.bsky.actor.getProfile",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"output": {
|
||||
"schema": {
|
||||
"ref": "app.bsky.actor.defs#profileViewDetailed",
|
||||
"type": "ref"
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"required": [
|
||||
"actor"
|
||||
],
|
||||
"properties": {
|
||||
"actor": {
|
||||
"type": "string",
|
||||
"format": "at-identifier",
|
||||
"description": "Handle or DID of account to fetch profile of."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"id": "app.bsky.actor.getProfiles",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"output": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"profiles"
|
||||
],
|
||||
"properties": {
|
||||
"profiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#profileViewDetailed",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"required": [
|
||||
"actors"
|
||||
],
|
||||
"properties": {
|
||||
"actors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "at-identifier"
|
||||
},
|
||||
"maxLength": 25
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Get detailed profile views of multiple actors."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"id": "app.bsky.actor.getSuggestions",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"output": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"actors"
|
||||
],
|
||||
"properties": {
|
||||
"recId": {
|
||||
"type": "integer",
|
||||
"description": "DEPRECATED: use recIdStr instead."
|
||||
},
|
||||
"actors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#profileView",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"cursor": {
|
||||
"type": "string"
|
||||
},
|
||||
"recIdStr": {
|
||||
"type": "string",
|
||||
"description": "Snowflake for this recommendation, use when submitting recommendation events."
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"default": 50,
|
||||
"maximum": 100,
|
||||
"minimum": 1
|
||||
},
|
||||
"cursor": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"id": "app.bsky.actor.profile",
|
||||
"defs": {
|
||||
"main": {
|
||||
"key": "literal:self",
|
||||
"type": "record",
|
||||
"record": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"type": "blob",
|
||||
"accept": [
|
||||
"image/png",
|
||||
"image/jpeg"
|
||||
],
|
||||
"maxSize": 1000000,
|
||||
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'"
|
||||
},
|
||||
"banner": {
|
||||
"type": "blob",
|
||||
"accept": [
|
||||
"image/png",
|
||||
"image/jpeg"
|
||||
],
|
||||
"maxSize": 1000000,
|
||||
"description": "Larger horizontal image to display behind profile view."
|
||||
},
|
||||
"labels": {
|
||||
"refs": [
|
||||
"com.atproto.label.defs#selfLabels"
|
||||
],
|
||||
"type": "union",
|
||||
"description": "Self-label values, specific to the Bluesky application, on the overall account."
|
||||
},
|
||||
"website": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"description": "Free-form pronouns text.",
|
||||
"maxGraphemes": 20
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"pinnedPost": {
|
||||
"ref": "com.atproto.repo.strongRef",
|
||||
"type": "ref"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 2560,
|
||||
"description": "Free-form profile description text.",
|
||||
"maxGraphemes": 256
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"maxLength": 640,
|
||||
"maxGraphemes": 64
|
||||
},
|
||||
"joinedViaStarterPack": {
|
||||
"ref": "com.atproto.repo.strongRef",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "A declaration of a Bluesky account profile."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "app.bsky.actor.putPreferences",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "procedure",
|
||||
"input": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"preferences"
|
||||
],
|
||||
"properties": {
|
||||
"preferences": {
|
||||
"ref": "app.bsky.actor.defs#preferences",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"description": "Set the private preferences attached to the account."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"id": "app.bsky.actor.searchActors",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"output": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"actors"
|
||||
],
|
||||
"properties": {
|
||||
"actors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#profileView",
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"cursor": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"properties": {
|
||||
"q": {
|
||||
"type": "string",
|
||||
"description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended."
|
||||
},
|
||||
"term": {
|
||||
"type": "string",
|
||||
"description": "DEPRECATED: use 'q' instead."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"default": 25,
|
||||
"maximum": 100,
|
||||
"minimum": 1
|
||||
},
|
||||
"cursor": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Find actors (profiles) matching search criteria. Does not require auth."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"id": "app.bsky.actor.searchActorsTypeahead",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"output": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"actors"
|
||||
],
|
||||
"properties": {
|
||||
"actors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "app.bsky.actor.defs#profileViewBasic",
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"properties": {
|
||||
"q": {
|
||||
"type": "string",
|
||||
"description": "Search query prefix; not a full query string."
|
||||
},
|
||||
"term": {
|
||||
"type": "string",
|
||||
"description": "DEPRECATED: use 'q' instead."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"maximum": 100,
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"id": "app.bsky.actor.status",
|
||||
"defs": {
|
||||
"live": {
|
||||
"type": "token",
|
||||
"description": "Advertises an account as currently offering live content."
|
||||
},
|
||||
"main": {
|
||||
"key": "literal:self",
|
||||
"type": "record",
|
||||
"record": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"status",
|
||||
"createdAt"
|
||||
],
|
||||
"properties": {
|
||||
"embed": {
|
||||
"refs": [
|
||||
"app.bsky.embed.external"
|
||||
],
|
||||
"type": "union",
|
||||
"description": "An optional embed associated with the status."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "The status for the account.",
|
||||
"knownValues": [
|
||||
"app.bsky.actor.status#live"
|
||||
]
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"durationMinutes": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "The duration of the status in minutes. Applications can choose to impose minimum and maximum limits."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "A declaration of a Bluesky account status."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"id": "app.bsky.ageassurance.begin",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "procedure",
|
||||
"input": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"email",
|
||||
"language",
|
||||
"countryCode"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "The user's email address to receive Age Assurance instructions."
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "The user's preferred language for communication during the Age Assurance process."
|
||||
},
|
||||
"regionCode": {
|
||||
"type": "string",
|
||||
"description": "An optional ISO 3166-2 code of the user's region or state within the country."
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string",
|
||||
"description": "An ISO 3166-1 alpha-2 code of the user's location."
|
||||
}
|
||||
}
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"errors": [
|
||||
{
|
||||
"name": "InvalidEmail"
|
||||
},
|
||||
{
|
||||
"name": "DidTooLong"
|
||||
},
|
||||
{
|
||||
"name": "InvalidInitiation"
|
||||
},
|
||||
{
|
||||
"name": "RegionNotSupported"
|
||||
}
|
||||
],
|
||||
"output": {
|
||||
"schema": {
|
||||
"ref": "app.bsky.ageassurance.defs#state",
|
||||
"type": "ref"
|
||||
},
|
||||
"encoding": "application/json"
|
||||
},
|
||||
"description": "Initiate Age Assurance for an account."
|
||||
}
|
||||
},
|
||||
"$type": "com.atproto.lexicon.schema",
|
||||
"lexicon": 1
|
||||
}
|
||||