-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/main' into starter-packs
* origin/main: Add a11y context (#4586) center pill text in label pill (#4579) Wait for AppView when posting (#4584) Bsky link card service (#4547)
- Loading branch information
Showing
22 changed files
with
1,909 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
name: build-and-push-ogcard-aws | ||
on: | ||
push: | ||
branches: | ||
- divy/bskycard | ||
|
||
env: | ||
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} | ||
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} | ||
PASSWORD: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_PASSWORD }} | ||
IMAGE_NAME: bskyogcard | ||
|
||
jobs: | ||
ogcard-container-aws: | ||
if: github.repository == 'bluesky-social/social-app' | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: read | ||
packages: write | ||
id-token: write | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Setup Docker buildx | ||
uses: docker/setup-buildx-action@v1 | ||
|
||
- name: Log into registry ${{ env.REGISTRY }} | ||
uses: docker/login-action@v2 | ||
with: | ||
registry: ${{ env.REGISTRY }} | ||
username: ${{ env.USERNAME}} | ||
password: ${{ env.PASSWORD }} | ||
|
||
- name: Extract Docker metadata | ||
id: meta | ||
uses: docker/metadata-action@v4 | ||
with: | ||
images: | | ||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
tags: | | ||
type=sha,enable=true,priority=100,prefix=,suffix=,format=long | ||
- name: Build and push Docker image | ||
id: build-and-push | ||
uses: docker/build-push-action@v4 | ||
with: | ||
context: . | ||
push: ${{ github.event_name != 'pull_request' }} | ||
file: ./Dockerfile.bskyogcard | ||
tags: ${{ steps.meta.outputs.tags }} | ||
labels: ${{ steps.meta.outputs.labels }} | ||
cache-from: type=gha | ||
cache-to: type=gha,mode=max |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
FROM node:20.11-alpine3.18 as build | ||
|
||
# Move files into the image and install | ||
WORKDIR /app | ||
|
||
COPY ./bskyogcard/package.json ./ | ||
COPY ./bskyogcard/yarn.lock ./ | ||
RUN yarn install --frozen-lockfile | ||
|
||
COPY ./bskyogcard ./ | ||
|
||
# build then prune dev deps | ||
RUN yarn build | ||
RUN yarn install --production --ignore-scripts --prefer-offline | ||
|
||
# Uses assets from build stage to reduce build size | ||
FROM node:20.11-alpine3.18 | ||
|
||
RUN apk add --update dumb-init | ||
|
||
# Avoid zombie processes, handle signal forwarding | ||
ENTRYPOINT ["dumb-init", "--"] | ||
|
||
WORKDIR /app | ||
COPY --from=build /app /app | ||
RUN mkdir /app/data && chown node /app/data | ||
|
||
VOLUME /app/data | ||
EXPOSE 3000 | ||
ENV CARD_PORT=3000 | ||
ENV NODE_ENV=production | ||
# potential perf issues w/ io_uring on this version of node | ||
ENV UV_USE_IO_URING=0 | ||
|
||
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user | ||
USER node | ||
CMD ["node", "--heapsnapshot-signal=SIGUSR2", "--enable-source-maps", "dist/bin.js"] | ||
|
||
LABEL org.opencontainers.image.source=https://github.com/bluesky-social/social-app | ||
LABEL org.opencontainers.image.description="Bsky Card Service" | ||
LABEL org.opencontainers.image.licenses=UNLICENSED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "bskyogcard", | ||
"version": "0.0.0", | ||
"type": "module", | ||
"main": "src/index.ts", | ||
"scripts": { | ||
"start": "node --loader ts-node/esm ./src/bin.ts", | ||
"build": "tsc && cp -r src/assets dist/assets" | ||
}, | ||
"dependencies": { | ||
"@atproto/api": "0.12.19-next.0", | ||
"@atproto/common": "^0.4.0", | ||
"@resvg/resvg-js": "^2.6.2", | ||
"express": "^4.19.2", | ||
"http-terminator": "^3.2.0", | ||
"pino": "^9.2.0", | ||
"react": "^18.3.1", | ||
"satori": "^0.10.13" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.14.3", | ||
"typescript": "^5.4.5" | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import cluster, {Worker} from 'node:cluster' | ||
|
||
import {envInt} from '@atproto/common' | ||
|
||
import {CardService, envToCfg, httpLogger, readEnv} from './index.js' | ||
|
||
async function main() { | ||
const env = readEnv() | ||
const cfg = envToCfg(env) | ||
const card = await CardService.create(cfg) | ||
await card.start() | ||
httpLogger.info('card service is running') | ||
process.on('SIGTERM', async () => { | ||
httpLogger.info('card service is stopping') | ||
await card.destroy() | ||
httpLogger.info('card service is stopped') | ||
if (cluster.isWorker) process.exit(0) | ||
}) | ||
} | ||
|
||
const workerCount = envInt('CARD_CLUSTER_WORKER_COUNT') | ||
|
||
if (workerCount) { | ||
if (cluster.isPrimary) { | ||
httpLogger.info(`primary ${process.pid} is running`) | ||
const workers = new Set<Worker>() | ||
for (let i = 0; i < workerCount; ++i) { | ||
workers.add(cluster.fork()) | ||
} | ||
let teardown = false | ||
cluster.on('exit', worker => { | ||
workers.delete(worker) | ||
if (!teardown) { | ||
workers.add(cluster.fork()) // restart on crash | ||
} | ||
}) | ||
process.on('SIGTERM', () => { | ||
teardown = true | ||
httpLogger.info('disconnecting workers') | ||
workers.forEach(w => w.kill('SIGTERM')) | ||
}) | ||
} else { | ||
httpLogger.info(`worker ${process.pid} is running`) | ||
main() | ||
} | ||
} else { | ||
main() // non-clustering | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import React from 'react' | ||
|
||
export function Butterfly(props: React.SVGAttributes<SVGSVGElement>) { | ||
return ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="none" | ||
viewBox="0 0 568 501" | ||
{...props}> | ||
<path | ||
fill="currentColor" | ||
d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z" | ||
/> | ||
</svg> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react' | ||
|
||
export function Img( | ||
props: Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src'> & {src: Buffer}, | ||
) { | ||
const {src, ...others} = props | ||
return ( | ||
<img {...others} src={`data:image/jpeg;base64,${src.toString('base64')}`} /> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* eslint-disable bsky-internal/avoid-unwrapped-text */ | ||
import React from 'react' | ||
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' | ||
|
||
import {Butterfly} from './Butterfly.js' | ||
import {Img} from './Img.js' | ||
|
||
export const STARTERPACK_HEIGHT = 630 | ||
export const STARTERPACK_WIDTH = 1200 | ||
export const TILE_SIZE = STARTERPACK_HEIGHT / 3 | ||
|
||
const GRADIENT_TOP = '#0A7AFF' | ||
const GRADIENT_BOTTOM = '#59B9FF' | ||
const IMAGE_STROKE = '#359CFF' | ||
|
||
export function StarterPack(props: { | ||
starterPack: AppBskyGraphDefs.StarterPackView | ||
images: Map<string, Buffer> | ||
}) { | ||
const {starterPack, images} = props | ||
const record = AppBskyGraphStarterpack.isRecord(starterPack.record) | ||
? starterPack.record | ||
: null | ||
const imagesArray = [...images.values()] | ||
const imageOfCreator = images.get(starterPack.creator.did) | ||
const imagesExceptCreator = [...images.entries()] | ||
.filter(([did]) => did !== starterPack.creator.did) | ||
.map(([, image]) => image) | ||
const imagesAcross: Buffer[] = [] | ||
if (imageOfCreator) { | ||
if (imagesExceptCreator.length >= 6) { | ||
imagesAcross.push(...imagesExceptCreator.slice(0, 3)) | ||
imagesAcross.push(imageOfCreator) | ||
imagesAcross.push(...imagesExceptCreator.slice(3, 6)) | ||
} else { | ||
const firstHalf = Math.floor(imagesExceptCreator.length / 2) | ||
imagesAcross.push(...imagesExceptCreator.slice(0, firstHalf)) | ||
imagesAcross.push(imageOfCreator) | ||
imagesAcross.push( | ||
...imagesExceptCreator.slice(firstHalf, imagesExceptCreator.length), | ||
) | ||
} | ||
} else { | ||
imagesAcross.push(...imagesExceptCreator.slice(0, 7)) | ||
} | ||
return ( | ||
<div | ||
style={{ | ||
display: 'flex', | ||
justifyContent: 'center', | ||
width: STARTERPACK_WIDTH, | ||
height: STARTERPACK_HEIGHT, | ||
backgroundColor: 'black', | ||
color: 'white', | ||
fontFamily: 'Inter', | ||
}}> | ||
{/* image tiles */} | ||
<div | ||
style={{ | ||
display: 'flex', | ||
flexWrap: 'wrap', | ||
alignItems: 'stretch', | ||
width: TILE_SIZE * 6, | ||
height: TILE_SIZE * 3, | ||
}}> | ||
{[...Array(18)].map((_, i) => { | ||
const image = imagesArray.at(i % imagesArray.length) | ||
return ( | ||
<div | ||
key={i} | ||
style={{ | ||
display: 'flex', | ||
height: TILE_SIZE, | ||
width: TILE_SIZE, | ||
}}> | ||
{image && <Img height="100%" width="100%" src={image} />} | ||
</div> | ||
) | ||
})} | ||
{/* background overlay */} | ||
<div | ||
style={{ | ||
display: 'flex', | ||
width: '100%', | ||
height: '100%', | ||
position: 'absolute', | ||
backgroundImage: `linear-gradient(to bottom, ${GRADIENT_TOP}, ${GRADIENT_BOTTOM})`, | ||
opacity: 0.9, | ||
}} | ||
/> | ||
</div> | ||
{/* foreground text & images */} | ||
<div | ||
style={{ | ||
display: 'flex', | ||
alignItems: 'center', | ||
flexDirection: 'column', | ||
width: '100%', | ||
height: '100%', | ||
position: 'absolute', | ||
color: 'white', | ||
}}> | ||
<div | ||
style={{ | ||
color: 'white', | ||
padding: 60, | ||
fontSize: 40, | ||
}}> | ||
JOIN THE CONVERSATION | ||
</div> | ||
<div style={{display: 'flex'}}> | ||
{imagesAcross.map((image, i) => { | ||
return ( | ||
<div | ||
key={i} | ||
style={{ | ||
display: 'flex', | ||
height: 172 + 15 * 2, | ||
width: 172 + 15 * 2, | ||
margin: -15, | ||
border: `15px solid ${IMAGE_STROKE}`, | ||
borderRadius: '50%', | ||
overflow: 'hidden', | ||
}}> | ||
<Img height="100%" width="100%" src={image} /> | ||
</div> | ||
) | ||
})} | ||
</div> | ||
<div | ||
style={{ | ||
padding: '75px 30px 0px', | ||
fontSize: 65, | ||
}}> | ||
{record?.name || 'Starter Pack'} | ||
</div> | ||
<div | ||
style={{ | ||
display: 'flex', | ||
fontSize: 40, | ||
justifyContent: 'center', | ||
padding: '30px 30px 10px', | ||
}}> | ||
on <Butterfly width="65" style={{margin: '-7px 10px 0'}} /> Bluesky | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import {envInt, envStr} from '@atproto/common' | ||
|
||
export type Config = { | ||
service: ServiceConfig | ||
} | ||
|
||
export type ServiceConfig = { | ||
port: number | ||
version?: string | ||
appviewUrl: string | ||
originVerify?: string | ||
} | ||
|
||
export type Environment = { | ||
port?: number | ||
version?: string | ||
appviewUrl?: string | ||
originVerify?: string | ||
} | ||
|
||
export const readEnv = (): Environment => { | ||
return { | ||
port: envInt('CARD_PORT'), | ||
version: envStr('CARD_VERSION'), | ||
appviewUrl: envStr('CARD_APPVIEW_URL'), | ||
originVerify: envStr('CARD_ORIGIN_VERIFY'), | ||
} | ||
} | ||
|
||
export const envToCfg = (env: Environment): Config => { | ||
const serviceCfg: ServiceConfig = { | ||
port: env.port ?? 3000, | ||
version: env.version, | ||
appviewUrl: env.appviewUrl ?? 'https://api.bsky.app', | ||
originVerify: env.originVerify, | ||
} | ||
return { | ||
service: serviceCfg, | ||
} | ||
} |
Oops, something went wrong.