Compare commits
172 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ddf2f5b31 | |||
| 5df7f4b113 | |||
| d7d2189a42 | |||
| 8ad46bfd61 | |||
| ec9db8470d | |||
| 0c4cae0148 | |||
| 98dc900cc5 | |||
| 742cfe6238 | |||
| 5ed88bd43e | |||
| d6d50818ee | |||
| 3349174bbd | |||
| e212a3f441 | |||
| 21123dab43 | |||
| e1d71bdbd6 | |||
| 5e14462a23 | |||
| 18b7c466cf | |||
| 8f06ba70bb | |||
| 56b688744e | |||
| 09bc4e95d8 | |||
| 149446a26e | |||
| 8c52d74925 | |||
| f45193783e | |||
| adcd066733 | |||
| b10a2b9a8e | |||
| 75da5f4965 | |||
| baa788de38 | |||
| 09dfc9edf8 | |||
| d5fd19df8f | |||
| 58e48fd31b | |||
| 3407206f52 | |||
| 1c6bfc02fb | |||
| d03dd8c815 | |||
| ca7386967a | |||
| d837f96478 | |||
| 12bf796293 | |||
| 4f02da96c8 | |||
| 0ed99b840d | |||
| aa7117edb6 | |||
| a3d4fb652b | |||
| 6694a33603 | |||
| dc3c81c4e2 | |||
| 04cfd06639 | |||
| 0598fc2faa | |||
| 6af78de9ee | |||
| cacc4c5068 | |||
| 14c2d75d49 | |||
| c133661768 | |||
| 63bb8fda2d | |||
| 4bb4452f08 | |||
| 0012c6d40f | |||
| a9fe87b842 | |||
| 1a037d3542 | |||
| 58a97db5b8 | |||
| 8ebf9cc4b1 | |||
| 91c4aa7c2d | |||
| 030c8e268e | |||
| d5ca95233e | |||
| fff3ae8f35 | |||
| d26928a5d8 | |||
| 58102377fd | |||
| 49396451ec | |||
| f6b138f709 | |||
| 0ab6d54093 | |||
| 5641a4393c | |||
| 878b0476dd | |||
| da4dfeb9cf | |||
| 3b0a177544 | |||
| 368cd7bb0e | |||
| c4dce4ce45 | |||
| 3f20e2e3cf | |||
| 83745c923f | |||
| 8621ecd38a | |||
| b23f112689 | |||
| dd2e173514 | |||
| 7c5ff79c8e | |||
| b75341314c | |||
| 58a9dceb33 | |||
| b6a38e7d64 | |||
| 4d9657c819 | |||
| d11b552710 | |||
| e5b9f130a9 | |||
| fc2fba0981 | |||
| 12faa005af | |||
| 363d18a40e | |||
| efbc6d4f1b | |||
| 682f31ec9d | |||
| 6cda641250 | |||
| 615c0c851e | |||
| 2ed133cb63 | |||
| f94edc3f44 | |||
| dd5198f317 | |||
| 340c2c5eaf | |||
| 795fe7455b | |||
| dc9e51dca1 | |||
| 59fa7c0c0d | |||
| d79891a858 | |||
| 51fca95669 | |||
| ed940c637e | |||
| 9e89ddeb1c | |||
| bce3338a02 | |||
| ffb67397e7 | |||
| 29aaf09a8b | |||
| e0ac7d5bdc | |||
| f64245c1fb | |||
| 873d91d466 | |||
| 77a512ae32 | |||
| f769564edf | |||
| 0a0c738790 | |||
| de1b7f7045 | |||
| 897427eed0 | |||
| 45648bd383 | |||
| 8eec632636 | |||
| f75a429f08 | |||
| f089f45781 | |||
| 21a7d47cdc | |||
| 35f64535cb | |||
| 1715afd80e | |||
| 7db8dd8980 | |||
| 707ea5bf06 | |||
| 4d9e686e3b | |||
| 4d6787009c | |||
| cb37647949 | |||
| 55812b0394 | |||
| ba21fddd78 | |||
| 8ff862798f | |||
| f344032ed8 | |||
| 4c48a1f14b | |||
| 4bba59790a | |||
| 4d8537bcd4 | |||
| 5d98b4b06c | |||
| 51f5e6bf90 | |||
| 39c32b0ab1 | |||
| eac4668d73 | |||
| 6ac8389adb | |||
| 52b52617e1 | |||
| 43adc64e5f | |||
| f927e74e52 | |||
| a33e370d22 | |||
| 80197556f1 | |||
| 75aec19230 | |||
| 7deea7ddd4 | |||
| bfcf872921 | |||
| e327bd01ec | |||
| 7d8fca56dc | |||
| 89d99a8701 | |||
| e170447cc6 | |||
| a98c4efc7c | |||
| 95f4cfe24f | |||
| 6316ddf627 | |||
| bdc1ea897f | |||
| a6d49062e6 | |||
| fad73fe928 | |||
| b06aa12730 | |||
| 0b24216040 | |||
| 7548c23f19 | |||
| b4e4c15fbb | |||
| 2a83ad1f5c | |||
| 035a1ca18e | |||
| c09b66182d | |||
| 741ce1c176 | |||
| 0af555830e | |||
| 45b10f2c60 | |||
| fa1dfe8b0a | |||
| 01f505d09f | |||
| b80594a554 | |||
| 3ea3b5a4d4 | |||
| 23a14454dc | |||
| 92fe3cde7a | |||
| ecd51bc6f9 | |||
| bd8f0e5a1c | |||
| 004b4e3969 | |||
| ff0b9e30de |
@@ -0,0 +1,53 @@
|
||||
name: build-and-push-link-aws
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
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: bskylink
|
||||
|
||||
jobs:
|
||||
link-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.bskylink
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,53 @@
|
||||
name: build-and-push-ogcard-aws
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
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
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up Go tooling
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
go-version: '1.22'
|
||||
- name: Dummy Static Files
|
||||
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt
|
||||
- name: Check
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Set up Go tooling
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
go-version: '1.22'
|
||||
- name: Dummy Static Files
|
||||
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt
|
||||
- name: Lint
|
||||
|
||||
@@ -115,3 +115,6 @@ src/locale/locales/**/*.js
|
||||
*.apk
|
||||
*.aab
|
||||
*.ipa
|
||||
|
||||
# ogcard assets
|
||||
bskyogcard/src/assets/fonts/noto-*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.21-bullseye AS build-env
|
||||
FROM golang:1.22-bullseye AS build-env
|
||||
|
||||
WORKDIR /usr/src/social-app
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
FROM node:20.11-alpine3.18 as build
|
||||
|
||||
# Move files into the image and install
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bskylink/package.json ./
|
||||
COPY ./bskylink/yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY ./bskylink ./
|
||||
|
||||
# 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 LINK_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 Link Service"
|
||||
LABEL org.opencontainers.image.licenses=UNLICENSED
|
||||
@@ -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 install-fonts && 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
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.21-bullseye AS build-env
|
||||
FROM golang:1.22-bullseye AS build-env
|
||||
|
||||
WORKDIR /usr/src/social-app
|
||||
|
||||
|
||||
@@ -132,8 +132,7 @@ appId: xyz.blueskyweb.app
|
||||
id: "feedItem-by-bob.test"
|
||||
- tapOn:
|
||||
id: "e2eGotoFeeds"
|
||||
- tapOn:
|
||||
id: "saved-feed-Good Ppl"
|
||||
- tapOn: "Good Ppl"
|
||||
- assertVisible:
|
||||
id: "feedItem-by-bob.test"
|
||||
- tapOn:
|
||||
@@ -144,8 +143,7 @@ appId: xyz.blueskyweb.app
|
||||
id: "homeScreenFeedTabs-Good Ppl"
|
||||
- tapOn:
|
||||
id: "e2eGotoLists"
|
||||
- tapOn:
|
||||
id: "list-Good Ppl"
|
||||
- tapOn: "Good Ppl"
|
||||
|
||||
- tapOn: "About"
|
||||
- assertVisible:
|
||||
@@ -171,8 +169,7 @@ appId: xyz.blueskyweb.app
|
||||
direction: LEFT
|
||||
- tapOn:
|
||||
id: "profilePager-selector-5"
|
||||
- tapOn:
|
||||
id: "list-Good Ppl"
|
||||
- tapOn: "Good Ppl"
|
||||
|
||||
- tapOn:
|
||||
label: "Adds and removes users on curatelists from the profile"
|
||||
|
||||
@@ -18,8 +18,7 @@ appId: xyz.blueskyweb.app
|
||||
direction: LEFT
|
||||
- tapOn:
|
||||
id: "profilePager-selector-4"
|
||||
- tapOn:
|
||||
id: "feed-alice-favs"
|
||||
- tapOn: "alice-favs"
|
||||
- tapOn: "Pin to Home"
|
||||
- tapOn:
|
||||
id: "bottomBarHomeBtn"
|
||||
|
||||
@@ -23,8 +23,7 @@ appId: xyz.blueskyweb.app
|
||||
direction: LEFT
|
||||
- tapOn:
|
||||
id: "profilePager-selector-4"
|
||||
- tapOn:
|
||||
id: "feed-alice-favs"
|
||||
- tapOn: "alice-favs"
|
||||
- tapOn: "Pin to Home"
|
||||
- tapOn:
|
||||
id: "bottomBarHomeBtn"
|
||||
|
||||
@@ -21,8 +21,7 @@ appId: xyz.blueskyweb.app
|
||||
direction: LEFT
|
||||
- tapOn:
|
||||
id: "profilePager-selector-4"
|
||||
- assertVisible:
|
||||
id: "feed-alice-favs"
|
||||
- assertVisible: "alice-favs"
|
||||
- swipe:
|
||||
from:
|
||||
id: "profilePager-selector"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import {RichText} from '@atproto/api'
|
||||
|
||||
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
|
||||
import {
|
||||
createStarterPackGooglePlayUri,
|
||||
createStarterPackLinkFromAndroidReferrer,
|
||||
parseStarterPackUri,
|
||||
} from 'lib/strings/starter-pack'
|
||||
import {cleanError} from '../../src/lib/strings/errors'
|
||||
import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles'
|
||||
import {enforceLen} from '../../src/lib/strings/helpers'
|
||||
@@ -796,3 +801,179 @@ describe('parseEmbedPlayerFromUrl', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('createStarterPackLinkFromAndroidReferrer', () => {
|
||||
const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey'
|
||||
|
||||
it('returns a link when input contains utm_source and utm_content', () => {
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey',
|
||||
),
|
||||
).toEqual(validOutput)
|
||||
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey',
|
||||
),
|
||||
).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey')
|
||||
})
|
||||
|
||||
it('returns a link when input contains utm_source and utm_content in different order', () => {
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky',
|
||||
),
|
||||
).toEqual(validOutput)
|
||||
})
|
||||
|
||||
it('returns a link when input contains other parameters as well', () => {
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey',
|
||||
),
|
||||
).toEqual(validOutput)
|
||||
})
|
||||
|
||||
it('returns null when utm_source is not present', () => {
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_content=starterpack_haileyok.com_rkey',
|
||||
),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when utm_content is not present', () => {
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when utm_content is malformed', () => {
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_content=starterpack_haileyok.com',
|
||||
),
|
||||
).toEqual(null)
|
||||
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'),
|
||||
).toEqual(null)
|
||||
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_content=starterpack_haileyok.com_rkey_more',
|
||||
),
|
||||
).toEqual(null)
|
||||
|
||||
expect(
|
||||
createStarterPackLinkFromAndroidReferrer(
|
||||
'utm_content=notastarterpack_haileyok.com_rkey',
|
||||
),
|
||||
).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseStarterPackHttpUri', () => {
|
||||
const baseUri = 'https://bsky.app/start'
|
||||
|
||||
it('returns a valid at uri when http uri is valid', () => {
|
||||
const validHttpUri = `${baseUri}/haileyok.com/rkey`
|
||||
expect(parseStarterPackUri(validHttpUri)).toEqual({
|
||||
name: 'haileyok.com',
|
||||
rkey: 'rkey',
|
||||
})
|
||||
|
||||
const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting`
|
||||
expect(parseStarterPackUri(validHttpUri2)).toEqual({
|
||||
name: 'haileyok.com',
|
||||
rkey: 'ilovetesting',
|
||||
})
|
||||
|
||||
const validHttpUri3 = `${baseUri}/testlover9000.com/rkey`
|
||||
expect(parseStarterPackUri(validHttpUri3)).toEqual({
|
||||
name: 'testlover9000.com',
|
||||
rkey: 'rkey',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null when there is no rkey', () => {
|
||||
const validHttpUri = `${baseUri}/haileyok.com`
|
||||
expect(parseStarterPackUri(validHttpUri)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when there is an extra path', () => {
|
||||
const validHttpUri = `${baseUri}/haileyok.com/rkey/other`
|
||||
expect(parseStarterPackUri(validHttpUri)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when there is no handle or rkey', () => {
|
||||
const validHttpUri = `${baseUri}`
|
||||
expect(parseStarterPackUri(validHttpUri)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when the route is not /start or /starter-pack', () => {
|
||||
const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey'
|
||||
expect(parseStarterPackUri(validHttpUri)).toEqual({
|
||||
name: 'haileyok.com',
|
||||
rkey: 'rkey',
|
||||
})
|
||||
|
||||
const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey'
|
||||
expect(parseStarterPackUri(validHttpUri2)).toEqual({
|
||||
name: 'haileyok.com',
|
||||
rkey: 'rkey',
|
||||
})
|
||||
|
||||
const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey'
|
||||
expect(parseStarterPackUri(invalidHttpUri)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns the at uri when the input is a valid starterpack at uri', () => {
|
||||
const validAtUri = 'at://did:123/app.bsky.graph.starterpack/rkey'
|
||||
expect(parseStarterPackUri(validAtUri)).toEqual({
|
||||
name: 'did:123',
|
||||
rkey: 'rkey',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null when the at uri has no rkey', () => {
|
||||
const validAtUri = 'at://did:123/app.bsky.graph.starterpack'
|
||||
expect(parseStarterPackUri(validAtUri)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when the collection is not app.bsky.graph.starterpack', () => {
|
||||
const validAtUri = 'at://did:123/app.bsky.graph.list/rkey'
|
||||
expect(parseStarterPackUri(validAtUri)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when the input is undefined', () => {
|
||||
expect(parseStarterPackUri(undefined)).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createStarterPackGooglePlayUri', () => {
|
||||
const base =
|
||||
'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_'
|
||||
|
||||
it('returns valid google play uri when input is valid', () => {
|
||||
expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual(
|
||||
`${base}name_rkey`,
|
||||
)
|
||||
})
|
||||
|
||||
it('returns null when no rkey is supplied', () => {
|
||||
// @ts-expect-error test
|
||||
expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when no name or rkey are supplied', () => {
|
||||
// @ts-expect-error test
|
||||
expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null)
|
||||
})
|
||||
|
||||
it('returns null when rkey is supplied but no name', () => {
|
||||
// @ts-expect-error test
|
||||
expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,6 +39,15 @@ module.exports = function (config) {
|
||||
const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight'
|
||||
const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production'
|
||||
|
||||
const ASSOCIATED_DOMAINS = [
|
||||
'applinks:bsky.app',
|
||||
'applinks:staging.bsky.app',
|
||||
'appclips:bsky.app',
|
||||
'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes
|
||||
// When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
|
||||
...(IS_DEV || IS_TESTFLIGHT ? [] : []),
|
||||
]
|
||||
|
||||
const UPDATES_CHANNEL = IS_TESTFLIGHT
|
||||
? 'testflight'
|
||||
: IS_PRODUCTION
|
||||
@@ -83,7 +92,7 @@ module.exports = function (config) {
|
||||
NSPhotoLibraryUsageDescription:
|
||||
'Used for profile pictures, posts, and other kinds of content',
|
||||
},
|
||||
associatedDomains: ['applinks:bsky.app', 'applinks:staging.bsky.app'],
|
||||
associatedDomains: ASSOCIATED_DOMAINS,
|
||||
splash: {
|
||||
...SPLASH_CONFIG,
|
||||
dark: DARK_SPLASH_CONFIG,
|
||||
@@ -202,6 +211,9 @@ module.exports = function (config) {
|
||||
sounds: PLATFORM === 'ios' ? ['assets/dm.aiff'] : ['assets/dm.mp3'],
|
||||
},
|
||||
],
|
||||
'expo-video',
|
||||
'react-native-compressor',
|
||||
'./plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
|
||||
'./plugins/withAndroidManifestPlugin.js',
|
||||
'./plugins/withAndroidManifestFCMIconPlugin.js',
|
||||
'./plugins/withAndroidStylesWindowBackgroundPlugin.js',
|
||||
@@ -234,6 +246,10 @@ module.exports = function (config) {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
targetName: 'BlueskyClip',
|
||||
bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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="M21 12a1 1 0 0 1-.293.707l-6 6a1 1 0 0 1-1.414-1.414L17.586 13H4a1 1 0 1 1 0-2h13.586l-4.293-4.293a1 1 0 0 1 1.414-1.414l6 6A1 1 0 0 1 21 12Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 284 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#080B12" fill-rule="evenodd" d="M3 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm6 0H5v4h4V5ZM3 15a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4Zm6 0H5v4h4v-4ZM13 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V5Zm6 0h-4v4h4V5ZM14 13a1 1 0 0 1 1 1v1h1a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1Zm3 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Zm0 4a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-1v1a1 1 0 1 1-2 0v-2Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 580 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="M11.26 5.227 5.02 6.899c-.734.197-1.17.95-.973 1.685l1.672 6.24c.197.734.951 1.17 1.685.973l6.24-1.672c.734-.197 1.17-.951.973-1.685L12.945 6.2a1.375 1.375 0 0 0-1.685-.973Zm-6.566.459a2.632 2.632 0 0 0-1.86 3.223l1.672 6.24a2.632 2.632 0 0 0 3.223 1.861l6.24-1.672a2.631 2.631 0 0 0 1.861-3.223l-1.672-6.24a2.632 2.632 0 0 0-3.223-1.861l-6.24 1.672Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M15.138 18.411a4.606 4.606 0 1 0 0-9.211 4.606 4.606 0 0 0 0 9.211Zm0 1.257a5.862 5.862 0 1 0 0-11.724 5.862 5.862 0 0 0 0 11.724Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1 @@
|
||||
<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.416a49.7 49.7 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.232c.246-.005.493-.007.741-.007 21.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.57 38.57 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>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -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="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 1v2h2V5H5Zm4 0v6h6V5H9Zm8 0v2h2V5h-2Zm2 4h-2v2h2V9Zm0 4h-2v2.444h2V13Zm0 4.444h-2V19h2v-1.556ZM15 19v-6H9v6h6Zm-8 0v-2H5v2h2Zm-2-4h2v-2H5v2Zm0-4h2V9H5v2Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 370 B |
|
After Width: | Height: | Size: 9.9 KiB |
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "bskylink",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "./tests/infra/with-test-db.sh node --loader ts-node/esm --test ./tests/index.ts",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/common": "^0.4.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"http-terminator": "^3.2.0",
|
||||
"kysely": "^0.27.3",
|
||||
"pg": "^8.12.0",
|
||||
"pino": "^9.2.0",
|
||||
"uint8arrays": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/pg": "^8.11.6",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {Database, envToCfg, httpLogger, LinkService, readEnv} from './index.js'
|
||||
|
||||
async function main() {
|
||||
const env = readEnv()
|
||||
const cfg = envToCfg(env)
|
||||
if (cfg.db.migrationUrl) {
|
||||
const migrateDb = Database.postgres({
|
||||
url: cfg.db.migrationUrl,
|
||||
schema: cfg.db.schema,
|
||||
})
|
||||
await migrateDb.migrateToLatestOrThrow()
|
||||
await migrateDb.close()
|
||||
}
|
||||
const link = await LinkService.create(cfg)
|
||||
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')
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
import {envInt, envList, envStr} from '@atproto/common'
|
||||
|
||||
export type Config = {
|
||||
service: ServiceConfig
|
||||
db: DbConfig
|
||||
}
|
||||
|
||||
export type ServiceConfig = {
|
||||
port: number
|
||||
version?: string
|
||||
hostnames: string[]
|
||||
appHostname: string
|
||||
}
|
||||
|
||||
export type DbConfig = {
|
||||
url: string
|
||||
migrationUrl?: string
|
||||
pool: DbPoolConfig
|
||||
schema?: string
|
||||
}
|
||||
|
||||
export type DbPoolConfig = {
|
||||
size: number
|
||||
maxUses: number
|
||||
idleTimeoutMs: number
|
||||
}
|
||||
|
||||
export type Environment = {
|
||||
port?: number
|
||||
version?: string
|
||||
hostnames: string[]
|
||||
appHostname?: string
|
||||
dbPostgresUrl?: string
|
||||
dbPostgresMigrationUrl?: string
|
||||
dbPostgresSchema?: string
|
||||
dbPostgresPoolSize?: number
|
||||
dbPostgresPoolMaxUses?: number
|
||||
dbPostgresPoolIdleTimeoutMs?: number
|
||||
}
|
||||
|
||||
export const readEnv = (): Environment => {
|
||||
return {
|
||||
port: envInt('LINK_PORT'),
|
||||
version: envStr('LINK_VERSION'),
|
||||
hostnames: envList('LINK_HOSTNAMES'),
|
||||
appHostname: envStr('LINK_APP_HOSTNAME'),
|
||||
dbPostgresUrl: envStr('LINK_DB_POSTGRES_URL'),
|
||||
dbPostgresMigrationUrl: envStr('LINK_DB_POSTGRES_MIGRATION_URL'),
|
||||
dbPostgresSchema: envStr('LINK_DB_POSTGRES_SCHEMA'),
|
||||
dbPostgresPoolSize: envInt('LINK_DB_POSTGRES_POOL_SIZE'),
|
||||
dbPostgresPoolMaxUses: envInt('LINK_DB_POSTGRES_POOL_MAX_USES'),
|
||||
dbPostgresPoolIdleTimeoutMs: envInt(
|
||||
'LINK_DB_POSTGRES_POOL_IDLE_TIMEOUT_MS',
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export const envToCfg = (env: Environment): Config => {
|
||||
const serviceCfg: ServiceConfig = {
|
||||
port: env.port ?? 3000,
|
||||
version: env.version,
|
||||
hostnames: env.hostnames,
|
||||
appHostname: env.appHostname || 'bsky.app',
|
||||
}
|
||||
if (!env.dbPostgresUrl) {
|
||||
throw new Error('Must configure postgres url (LINK_DB_POSTGRES_URL)')
|
||||
}
|
||||
const dbCfg: DbConfig = {
|
||||
url: env.dbPostgresUrl,
|
||||
migrationUrl: env.dbPostgresMigrationUrl,
|
||||
schema: env.dbPostgresSchema,
|
||||
pool: {
|
||||
idleTimeoutMs: env.dbPostgresPoolIdleTimeoutMs ?? 10000,
|
||||
maxUses: env.dbPostgresPoolMaxUses ?? Infinity,
|
||||
size: env.dbPostgresPoolSize ?? 10,
|
||||
},
|
||||
}
|
||||
return {
|
||||
service: serviceCfg,
|
||||
db: dbCfg,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {Config} from './config.js'
|
||||
import Database from './db/index.js'
|
||||
|
||||
export type AppContextOptions = {
|
||||
cfg: Config
|
||||
db: Database
|
||||
}
|
||||
|
||||
export class AppContext {
|
||||
cfg: Config
|
||||
db: Database
|
||||
abortController = new AbortController()
|
||||
|
||||
constructor(private opts: AppContextOptions) {
|
||||
this.cfg = this.opts.cfg
|
||||
this.db = this.opts.db
|
||||
}
|
||||
|
||||
static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
|
||||
const db = Database.postgres({
|
||||
url: cfg.db.url,
|
||||
schema: cfg.db.schema,
|
||||
poolSize: cfg.db.pool.size,
|
||||
poolMaxUses: cfg.db.pool.maxUses,
|
||||
poolIdleTimeoutMs: cfg.db.pool.idleTimeoutMs,
|
||||
})
|
||||
return new AppContext({
|
||||
cfg,
|
||||
db,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import assert from 'assert'
|
||||
import {
|
||||
Kysely,
|
||||
KyselyPlugin,
|
||||
Migrator,
|
||||
PluginTransformQueryArgs,
|
||||
PluginTransformResultArgs,
|
||||
PostgresDialect,
|
||||
QueryResult,
|
||||
RootOperationNode,
|
||||
UnknownRow,
|
||||
} from 'kysely'
|
||||
import {default as Pg} from 'pg'
|
||||
|
||||
import {dbLogger as log} from '../logger.js'
|
||||
import {default as migrations} from './migrations/index.js'
|
||||
import {DbMigrationProvider} from './migrations/provider.js'
|
||||
import {DbSchema} from './schema.js'
|
||||
|
||||
export class Database {
|
||||
migrator: Migrator
|
||||
destroyed = false
|
||||
|
||||
constructor(public db: Kysely<DbSchema>, public cfg: PgConfig) {
|
||||
this.migrator = new Migrator({
|
||||
db,
|
||||
migrationTableSchema: cfg.schema,
|
||||
provider: new DbMigrationProvider(migrations),
|
||||
})
|
||||
}
|
||||
|
||||
static postgres(opts: PgOptions): Database {
|
||||
const {schema, url, txLockNonce} = opts
|
||||
const pool =
|
||||
opts.pool ??
|
||||
new Pg.Pool({
|
||||
connectionString: url,
|
||||
max: opts.poolSize,
|
||||
maxUses: opts.poolMaxUses,
|
||||
idleTimeoutMillis: opts.poolIdleTimeoutMs,
|
||||
})
|
||||
|
||||
// Select count(*) and other pg bigints as js integer
|
||||
Pg.types.setTypeParser(Pg.types.builtins.INT8, n => parseInt(n, 10))
|
||||
|
||||
// Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema)
|
||||
if (schema && !/^[a-z_]+$/i.test(schema)) {
|
||||
throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`)
|
||||
}
|
||||
|
||||
pool.on('error', onPoolError)
|
||||
|
||||
const db = new Kysely<DbSchema>({
|
||||
dialect: new PostgresDialect({pool}),
|
||||
})
|
||||
|
||||
return new Database(db, {
|
||||
pool,
|
||||
schema,
|
||||
url,
|
||||
txLockNonce,
|
||||
})
|
||||
}
|
||||
|
||||
async transaction<T>(fn: (db: Database) => Promise<T>): Promise<T> {
|
||||
const leakyTxPlugin = new LeakyTxPlugin()
|
||||
return this.db
|
||||
.withPlugin(leakyTxPlugin)
|
||||
.transaction()
|
||||
.execute(txn => {
|
||||
const dbTxn = new Database(txn, this.cfg)
|
||||
return fn(dbTxn)
|
||||
.catch(async err => {
|
||||
leakyTxPlugin.endTx()
|
||||
// ensure that all in-flight queries are flushed & the connection is open
|
||||
await dbTxn.db.getExecutor().provideConnection(async () => {})
|
||||
throw err
|
||||
})
|
||||
.finally(() => leakyTxPlugin.endTx())
|
||||
})
|
||||
}
|
||||
|
||||
get schema(): string | undefined {
|
||||
return this.cfg.schema
|
||||
}
|
||||
|
||||
get isTransaction() {
|
||||
return this.db.isTransaction
|
||||
}
|
||||
|
||||
assertTransaction() {
|
||||
assert(this.isTransaction, 'Transaction required')
|
||||
}
|
||||
|
||||
assertNotTransaction() {
|
||||
assert(!this.isTransaction, 'Cannot be in a transaction')
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.destroyed) return
|
||||
await this.db.destroy()
|
||||
this.destroyed = true
|
||||
}
|
||||
|
||||
async migrateToOrThrow(migration: string) {
|
||||
if (this.schema) {
|
||||
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
|
||||
}
|
||||
const {error, results} = await this.migrator.migrateTo(migration)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (!results) {
|
||||
throw new Error('An unknown failure occurred while migrating')
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
async migrateToLatestOrThrow() {
|
||||
if (this.schema) {
|
||||
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
|
||||
}
|
||||
const {error, results} = await this.migrator.migrateToLatest()
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (!results) {
|
||||
throw new Error('An unknown failure occurred while migrating')
|
||||
}
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
||||
export default Database
|
||||
|
||||
export type PgConfig = {
|
||||
pool: Pg.Pool
|
||||
url: string
|
||||
schema?: string
|
||||
txLockNonce?: string
|
||||
}
|
||||
|
||||
type PgOptions = {
|
||||
url: string
|
||||
pool?: Pg.Pool
|
||||
schema?: string
|
||||
poolSize?: number
|
||||
poolMaxUses?: number
|
||||
poolIdleTimeoutMs?: number
|
||||
txLockNonce?: string
|
||||
}
|
||||
|
||||
class LeakyTxPlugin implements KyselyPlugin {
|
||||
private txOver = false
|
||||
|
||||
endTx() {
|
||||
this.txOver = true
|
||||
}
|
||||
|
||||
transformQuery(args: PluginTransformQueryArgs): RootOperationNode {
|
||||
if (this.txOver) {
|
||||
throw new Error('tx already failed')
|
||||
}
|
||||
return args.node
|
||||
}
|
||||
|
||||
async transformResult(
|
||||
args: PluginTransformResultArgs,
|
||||
): Promise<QueryResult<UnknownRow>> {
|
||||
return args.result
|
||||
}
|
||||
}
|
||||
|
||||
const onPoolError = (err: Error) => log.error({err}, 'db pool error')
|
||||
@@ -0,0 +1,15 @@
|
||||
import {Kysely} from 'kysely'
|
||||
|
||||
export async function up(db: Kysely<unknown>): Promise<void> {
|
||||
await db.schema
|
||||
.createTable('link')
|
||||
.addColumn('id', 'varchar', col => col.primaryKey())
|
||||
.addColumn('type', 'smallint', col => col.notNull()) // integer enum: 1->starterpack
|
||||
.addColumn('path', 'varchar', col => col.notNull())
|
||||
.addUniqueConstraint('link_path_unique', ['path'])
|
||||
.execute()
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<unknown>): Promise<void> {
|
||||
await db.schema.dropTable('link').execute()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import * as init from './001-init.js'
|
||||
|
||||
export default {
|
||||
'001': init,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {Migration, MigrationProvider} from 'kysely'
|
||||
|
||||
export class DbMigrationProvider implements MigrationProvider {
|
||||
constructor(private migrations: Record<string, Migration>) {}
|
||||
async getMigrations(): Promise<Record<string, Migration>> {
|
||||
return this.migrations
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {Selectable} from 'kysely'
|
||||
|
||||
export type DbSchema = {
|
||||
link: Link
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
id: string
|
||||
type: LinkType
|
||||
path: string
|
||||
}
|
||||
|
||||
export enum LinkType {
|
||||
StarterPack = 1,
|
||||
}
|
||||
|
||||
export type LinkEntry = Selectable<Link>
|
||||
@@ -0,0 +1,45 @@
|
||||
import events from 'node:events'
|
||||
import http from 'node:http'
|
||||
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import {createHttpTerminator, HttpTerminator} from 'http-terminator'
|
||||
|
||||
import {Config} from './config.js'
|
||||
import {AppContext} from './context.js'
|
||||
import {default as routes, errorHandler} from './routes/index.js'
|
||||
|
||||
export * from './config.js'
|
||||
export * from './db/index.js'
|
||||
export * from './logger.js'
|
||||
|
||||
export class LinkService {
|
||||
public server?: http.Server
|
||||
private terminator?: HttpTerminator
|
||||
|
||||
constructor(public app: express.Application, public ctx: AppContext) {}
|
||||
|
||||
static async create(cfg: Config): Promise<LinkService> {
|
||||
let app = express()
|
||||
app.use(cors())
|
||||
|
||||
const ctx = await AppContext.fromConfig(cfg)
|
||||
app = routes(ctx, app)
|
||||
app.use(errorHandler)
|
||||
|
||||
return new LinkService(app, ctx)
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.server = this.app.listen(this.ctx.cfg.service.port)
|
||||
this.server.keepAliveTimeout = 90000
|
||||
this.terminator = createHttpTerminator({server: this.server})
|
||||
await events.once(this.server, 'listening')
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
this.ctx.abortController.abort()
|
||||
await this.terminator?.terminate()
|
||||
await this.ctx.db.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import {subsystemLogger} from '@atproto/common'
|
||||
|
||||
export const httpLogger = subsystemLogger('bskylink')
|
||||
export const dbLogger = subsystemLogger('bskylink:db')
|
||||
@@ -0,0 +1,111 @@
|
||||
import assert from 'node:assert'
|
||||
|
||||
import bodyParser from 'body-parser'
|
||||
import {Express, Request} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {LinkType} from '../db/schema.js'
|
||||
import {randomId} from '../util.js'
|
||||
import {handler} from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.post(
|
||||
'/link',
|
||||
bodyParser.json(),
|
||||
handler(async (req, res) => {
|
||||
let path: string
|
||||
if (typeof req.body?.path === 'string') {
|
||||
path = req.body.path
|
||||
} else {
|
||||
return res.status(400).json({
|
||||
error: 'InvalidPath',
|
||||
message: '"path" parameter is missing or not a string',
|
||||
})
|
||||
}
|
||||
if (!path.startsWith('/')) {
|
||||
return res.status(400).json({
|
||||
error: 'InvalidPath',
|
||||
message:
|
||||
'"path" parameter must be formatted as a path, starting with a "/"',
|
||||
})
|
||||
}
|
||||
const parts = getPathParts(path)
|
||||
if (parts.length === 3 && parts[0] === 'start') {
|
||||
// link pattern: /start/{did}/{rkey}
|
||||
if (!parts[1].startsWith('did:')) {
|
||||
// enforce strong links
|
||||
return res.status(400).json({
|
||||
error: 'InvalidPath',
|
||||
message:
|
||||
'"path" parameter for starter pack must contain the actor\'s DID',
|
||||
})
|
||||
}
|
||||
const id = await ensureLink(ctx, LinkType.StarterPack, parts)
|
||||
return res.json({url: getUrl(ctx, req, id)})
|
||||
}
|
||||
return res.status(400).json({
|
||||
error: 'InvalidPath',
|
||||
message: '"path" parameter does not have a known format',
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const ensureLink = async (ctx: AppContext, type: LinkType, parts: string[]) => {
|
||||
const normalizedPath = normalizedPathFromParts(parts)
|
||||
const created = await ctx.db.db
|
||||
.insertInto('link')
|
||||
.values({
|
||||
id: randomId(),
|
||||
type,
|
||||
path: normalizedPath,
|
||||
})
|
||||
.onConflict(oc => oc.column('path').doNothing())
|
||||
.returningAll()
|
||||
.executeTakeFirst()
|
||||
if (created) {
|
||||
return created.id
|
||||
}
|
||||
const found = await ctx.db.db
|
||||
.selectFrom('link')
|
||||
.selectAll()
|
||||
.where('path', '=', normalizedPath)
|
||||
.executeTakeFirstOrThrow()
|
||||
return found.id
|
||||
}
|
||||
|
||||
const getUrl = (ctx: AppContext, req: Request, id: string) => {
|
||||
if (!ctx.cfg.service.hostnames.length) {
|
||||
assert(req.headers.host, 'request must be made with host header')
|
||||
const baseUrl =
|
||||
req.protocol === 'http' && req.headers.host.startsWith('localhost:')
|
||||
? `http://${req.headers.host}`
|
||||
: `https://${req.headers.host}`
|
||||
return `${baseUrl}/${id}`
|
||||
}
|
||||
const baseUrl = ctx.cfg.service.hostnames.includes(req.headers.host)
|
||||
? `https://${req.headers.host}`
|
||||
: `https://${ctx.cfg.service.hostnames[0]}`
|
||||
return `${baseUrl}/${id}`
|
||||
}
|
||||
|
||||
const normalizedPathFromParts = (parts: string[]): string => {
|
||||
return (
|
||||
'/' +
|
||||
parts
|
||||
.map(encodeURIComponent)
|
||||
.map(part => part.replaceAll('%3A', ':')) // preserve colons
|
||||
.join('/')
|
||||
)
|
||||
}
|
||||
|
||||
const getPathParts = (path: string): string[] => {
|
||||
if (path === '/') return []
|
||||
if (path.endsWith('/')) {
|
||||
path = path.slice(0, -1) // ignore trailing slash
|
||||
}
|
||||
return path
|
||||
.slice(1) // remove leading slash
|
||||
.split('/')
|
||||
.map(decodeURIComponent)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {Express} from 'express'
|
||||
import {sql} from 'kysely'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {handler} from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get(
|
||||
'/_health',
|
||||
handler(async (_req, res) => {
|
||||
const {version} = ctx.cfg.service
|
||||
try {
|
||||
await sql`select 1`.execute(ctx.db.db)
|
||||
return res.send({version})
|
||||
} catch (err) {
|
||||
return res.status(503).send({version, error: 'Service Unavailable'})
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {Express} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {default as create} from './create.js'
|
||||
import {default as health} from './health.js'
|
||||
import {default as redirect} from './redirect.js'
|
||||
import {default as siteAssociation} from './siteAssociation.js'
|
||||
|
||||
export * from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
app = health(ctx, app) // GET /_health
|
||||
app = siteAssociation(ctx, app) // GET /.well-known/apple-app-site-association
|
||||
app = create(ctx, app) // POST /link
|
||||
app = redirect(ctx, app) // GET /:linkId (should go last due to permissive matching)
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from 'node:assert'
|
||||
|
||||
import {DAY, SECOND} from '@atproto/common'
|
||||
import {Express} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {handler} from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get(
|
||||
'/:linkId',
|
||||
handler(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()
|
||||
if (!found) {
|
||||
// potentially broken or mistyped link
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
if (contentType === 'json') {
|
||||
return res
|
||||
.status(404)
|
||||
.json({
|
||||
error: 'NotFound',
|
||||
message: 'Link not found',
|
||||
})
|
||||
.end()
|
||||
}
|
||||
// send the user to the app
|
||||
res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`)
|
||||
return res.status(302).end()
|
||||
}
|
||||
// build url from original url in order to preserve query params
|
||||
const url = new URL(
|
||||
req.originalUrl,
|
||||
`https://${ctx.cfg.service.appHostname}`,
|
||||
)
|
||||
url.pathname = found.path
|
||||
res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`)
|
||||
if (contentType === 'json') {
|
||||
return res.json({url: url.href}).end()
|
||||
}
|
||||
res.setHeader('Location', url.href)
|
||||
return res.status(301).end()
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import {Express} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get('/.well-known/apple-app-site-association', (req, res) => {
|
||||
res.json({
|
||||
applinks: {
|
||||
apps: [],
|
||||
details: [
|
||||
{
|
||||
appID: 'B3LX46C5HS.xyz.blueskyweb.app',
|
||||
paths: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
appclips: {
|
||||
apps: ['B3LX46C5HS.xyz.blueskyweb.app.AppClip'],
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ErrorRequestHandler, Request, RequestHandler, Response} from 'express'
|
||||
|
||||
import {httpLogger} from '../logger.js'
|
||||
|
||||
export type Handler = (req: Request, res: Response) => Awaited<void>
|
||||
|
||||
export const handler = (runHandler: Handler): RequestHandler => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
await runHandler(req, res)
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const errorHandler: ErrorRequestHandler = (err, _req, res, next) => {
|
||||
httpLogger.error({err}, 'request error')
|
||||
if (res.headersSent) {
|
||||
return next(err)
|
||||
}
|
||||
return res.status(500).end('server error')
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {randomBytes} from 'node:crypto'
|
||||
|
||||
import {toString} from 'uint8arrays'
|
||||
|
||||
// 40bit random id of 5-7 characters
|
||||
export const randomId = () => {
|
||||
return toString(randomBytes(5), 'base58btc')
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import assert from 'node:assert'
|
||||
import {AddressInfo} from 'node:net'
|
||||
import {after, before, describe, it} from 'node:test'
|
||||
|
||||
import {Database, envToCfg, LinkService, readEnv} from '../src/index.js'
|
||||
|
||||
describe('link service', async () => {
|
||||
let linkService: LinkService
|
||||
let baseUrl: string
|
||||
before(async () => {
|
||||
const env = readEnv()
|
||||
const cfg = envToCfg({
|
||||
...env,
|
||||
hostnames: ['test.bsky.link'],
|
||||
appHostname: 'test.bsky.app',
|
||||
dbPostgresSchema: 'link_test',
|
||||
dbPostgresUrl: process.env.DB_POSTGRES_URL,
|
||||
})
|
||||
const migrateDb = Database.postgres({
|
||||
url: cfg.db.url,
|
||||
schema: cfg.db.schema,
|
||||
})
|
||||
await migrateDb.migrateToLatestOrThrow()
|
||||
await migrateDb.close()
|
||||
linkService = await LinkService.create(cfg)
|
||||
await linkService.start()
|
||||
const {port} = linkService.server?.address() as AddressInfo
|
||||
baseUrl = `http://localhost:${port}`
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await linkService?.destroy()
|
||||
})
|
||||
|
||||
it('creates a starter pack link', async () => {
|
||||
const link = await getLink('/start/did:example:alice/xxx')
|
||||
const url = new URL(link)
|
||||
assert.strictEqual(url.origin, 'https://test.bsky.link')
|
||||
assert.match(url.pathname, /^\/[a-z0-9]+$/i)
|
||||
})
|
||||
|
||||
it('normalizes input paths and provides same link each time.', async () => {
|
||||
const link1 = await getLink('/start/did%3Aexample%3Abob/yyy')
|
||||
const link2 = await getLink('/start/did:example:bob/yyy/')
|
||||
assert.strictEqual(link1, link2)
|
||||
})
|
||||
|
||||
it('serves permanent redirect, preserving query params.', async () => {
|
||||
const link = await getLink('/start/did:example:carol/zzz/')
|
||||
const [status, location] = await getRedirect(`${link}?a=b`)
|
||||
assert.strictEqual(status, 301)
|
||||
const locationUrl = new URL(location)
|
||||
assert.strictEqual(
|
||||
locationUrl.pathname + locationUrl.search,
|
||||
'/start/did:example:carol/zzz?a=b',
|
||||
)
|
||||
})
|
||||
|
||||
it('returns json object with url when requested', async () => {
|
||||
const link = await getLink('/start/did:example:carol/zzz/')
|
||||
const [status, json] = await getJsonRedirect(link)
|
||||
assert.strictEqual(status, 200)
|
||||
assert(json.url)
|
||||
const url = new URL(json.url)
|
||||
assert.strictEqual(url.pathname, '/start/did:example:carol/zzz')
|
||||
})
|
||||
|
||||
it('returns 404 for unknown link when requesting json', async () => {
|
||||
const [status, json] = await getJsonRedirect(
|
||||
'https://test.bsky.link/unknown',
|
||||
)
|
||||
assert(json.error)
|
||||
assert(json.message)
|
||||
assert.strictEqual(status, 404)
|
||||
assert.strictEqual(json.error, 'NotFound')
|
||||
assert.strictEqual(json.message, 'Link not found')
|
||||
})
|
||||
|
||||
async function getRedirect(link: string): Promise<[number, string]> {
|
||||
const url = new URL(link)
|
||||
const base = new URL(baseUrl)
|
||||
url.protocol = base.protocol
|
||||
url.host = base.host
|
||||
const res = await fetch(url, {redirect: 'manual'})
|
||||
await res.arrayBuffer() // drain
|
||||
assert(
|
||||
res.status === 301 || res.status === 303,
|
||||
'response was not a redirect',
|
||||
)
|
||||
return [res.status, res.headers.get('location') ?? '']
|
||||
}
|
||||
|
||||
async function getJsonRedirect(
|
||||
link: string,
|
||||
): Promise<[number, {url?: string; error?: string; message?: string}]> {
|
||||
const url = new URL(link)
|
||||
const base = new URL(baseUrl)
|
||||
url.protocol = base.protocol
|
||||
url.host = base.host
|
||||
const res = await fetch(url, {
|
||||
redirect: 'manual',
|
||||
headers: {accept: 'application/json,text/html'},
|
||||
})
|
||||
assert(
|
||||
res.headers.get('content-type')?.startsWith('application/json'),
|
||||
'content type was not json',
|
||||
)
|
||||
const json = await res.json()
|
||||
return [res.status, json]
|
||||
}
|
||||
|
||||
async function getLink(path: string): Promise<string> {
|
||||
const res = await fetch(new URL('/link', baseUrl), {
|
||||
method: 'post',
|
||||
headers: {'content-type': 'application/json'},
|
||||
body: JSON.stringify({path}),
|
||||
})
|
||||
assert.strictEqual(res.status, 200)
|
||||
const payload = await res.json()
|
||||
assert(typeof payload.url === 'string')
|
||||
return payload.url
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Exit if any command fails
|
||||
set -e
|
||||
|
||||
get_container_id() {
|
||||
local compose_file=$1
|
||||
local service=$2
|
||||
if [ -z "${compose_file}" ] || [ -z "${service}" ]; then
|
||||
echo "usage: get_container_id <compose_file> <service>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# first line of jq normalizes for docker compose breaking change, see docker/compose#10958
|
||||
docker compose --file $compose_file ps --format json --status running \
|
||||
| jq -sc '.[] | if type=="array" then .[] else . end' | jq -s \
|
||||
| jq -r '.[]? | select(.Service == "'${service}'") | .ID'
|
||||
}
|
||||
|
||||
# Exports all environment variables
|
||||
export_env() {
|
||||
export_pg_env
|
||||
}
|
||||
|
||||
# Exports postgres environment variables
|
||||
export_pg_env() {
|
||||
# Based on creds in compose.yaml
|
||||
export PGPORT=5433
|
||||
export PGHOST=localhost
|
||||
export PGUSER=pg
|
||||
export PGPASSWORD=password
|
||||
export PGDATABASE=postgres
|
||||
export DB_POSTGRES_URL="postgresql://pg:password@127.0.0.1:5433/postgres"
|
||||
}
|
||||
|
||||
|
||||
pg_clear() {
|
||||
local pg_uri=$1
|
||||
|
||||
for schema_name in `psql "${pg_uri}" -c "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name NOT LIKE 'information_schema';" -t`; do
|
||||
psql "${pg_uri}" -c "DROP SCHEMA \"${schema_name}\" CASCADE;"
|
||||
done
|
||||
}
|
||||
|
||||
pg_init() {
|
||||
local pg_uri=$1
|
||||
|
||||
psql "${pg_uri}" -c "CREATE SCHEMA IF NOT EXISTS \"public\";"
|
||||
}
|
||||
|
||||
main_native() {
|
||||
local services=${SERVICES}
|
||||
local postgres_url_env_var=`[[ $services == *"db_test"* ]] && echo "DB_TEST_POSTGRES_URL" || echo "DB_POSTGRES_URL"`
|
||||
|
||||
postgres_url="${!postgres_url_env_var}"
|
||||
|
||||
if [ -n "${postgres_url}" ]; then
|
||||
echo "Using ${postgres_url_env_var} (${postgres_url}) to connect to postgres."
|
||||
pg_init "${postgres_url}"
|
||||
else
|
||||
echo "Postgres connection string missing did you set ${postgres_url_env_var}?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
local services=$@
|
||||
|
||||
if [ -n "${postgres_url}" ] && [[ $services == *"db_test"* ]]; then
|
||||
pg_clear "${postgres_url}" &> /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# trap SIGINT and performs cleanup
|
||||
trap "on_sigint ${services}" INT
|
||||
on_sigint() {
|
||||
cleanup $@
|
||||
exit $?
|
||||
}
|
||||
|
||||
# Run the arguments as a command
|
||||
DB_POSTGRES_URL="${postgres_url}" \
|
||||
"$@"
|
||||
code=$?
|
||||
|
||||
cleanup ${services}
|
||||
|
||||
exit ${code}
|
||||
}
|
||||
|
||||
main_docker() {
|
||||
# Expect a SERVICES env var to be set with the docker service names
|
||||
local services=${SERVICES}
|
||||
|
||||
dir=$(dirname $0)
|
||||
compose_file="${dir}/docker-compose.yaml"
|
||||
|
||||
# whether this particular script started the container(s)
|
||||
started_container=false
|
||||
|
||||
# performs cleanup as necessary, i.e. taking down containers
|
||||
# if this script started them
|
||||
cleanup() {
|
||||
local services=$@
|
||||
echo # newline
|
||||
if $started_container; then
|
||||
docker compose --file $compose_file rm --force --stop --volumes ${services}
|
||||
fi
|
||||
}
|
||||
|
||||
# trap SIGINT and performs cleanup
|
||||
trap "on_sigint ${services}" INT
|
||||
on_sigint() {
|
||||
cleanup $@
|
||||
exit $?
|
||||
}
|
||||
|
||||
# check if all services are running already
|
||||
not_running=false
|
||||
for service in $services; do
|
||||
container_id=$(get_container_id $compose_file $service)
|
||||
if [ -z $container_id ]; then
|
||||
not_running=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# if any are missing, recreate all services
|
||||
if $not_running; then
|
||||
started_container=true
|
||||
docker compose --file $compose_file up --wait --force-recreate ${services}
|
||||
else
|
||||
echo "all services ${services} are already running"
|
||||
fi
|
||||
|
||||
# do not exit when following commands fail, so we can intercept exit code & tear down docker
|
||||
set +e
|
||||
|
||||
# setup environment variables and run args
|
||||
export_env
|
||||
"$@"
|
||||
# save return code for later
|
||||
code=$?
|
||||
|
||||
# performs cleanup as necessary
|
||||
cleanup ${services}
|
||||
exit ${code}
|
||||
}
|
||||
|
||||
# Main entry point
|
||||
main() {
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
echo "Docker unavailable. Running on host."
|
||||
main_native $@
|
||||
else
|
||||
main_docker $@
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
# An ephermerally-stored postgres database for single-use test runs
|
||||
db_test: &db_test
|
||||
image: postgres:14.11-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=pg
|
||||
- POSTGRES_PASSWORD=password
|
||||
ports:
|
||||
- '5433:5432'
|
||||
# Healthcheck ensures db is queryable when `docker-compose up --wait` completes
|
||||
healthcheck:
|
||||
test: 'pg_isready -U pg'
|
||||
interval: 500ms
|
||||
timeout: 10s
|
||||
retries: 20
|
||||
# A persistently-stored postgres database
|
||||
db:
|
||||
<<: *db_test
|
||||
ports:
|
||||
- '5432:5432'
|
||||
healthcheck:
|
||||
disable: true
|
||||
volumes:
|
||||
- link_db:/var/lib/postgresql/data
|
||||
volumes:
|
||||
link_db:
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Example usage:
|
||||
# ./with-test-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;'
|
||||
|
||||
dir=$(dirname $0)
|
||||
. ${dir}/_common.sh
|
||||
|
||||
SERVICES="db_test" main "$@"
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"lib": ["ES2021.String"]
|
||||
},
|
||||
"include": ["./src/index.ts", "./src/bin.ts"]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "bskyogcard",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "node --loader ts-node/esm ./src/bin.ts",
|
||||
"dev": "node --watch-path ./src --loader ts-node/esm ./src/bin.ts",
|
||||
"build": "tsc && cp -r src/assets dist/",
|
||||
"install-fonts": "node --loader ts-node/esm scripts/install-fonts.ts"
|
||||
},
|
||||
"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",
|
||||
"twemoji": "^14.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {writeFile} from 'node:fs/promises'
|
||||
import * as path from 'node:path'
|
||||
import {fileURLToPath} from 'node:url'
|
||||
|
||||
const __DIRNAME = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const FONTS = [
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-jp@5.0/japanese-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-tc@5.0/chinese-traditional-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-sc@5.0/chinese-simplified-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-hk@5.0/chinese-hongkong-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-kr@5.0/korean-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-thai@5.0/thai-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-arabic@5.0/arabic-700-normal.ttf',
|
||||
'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-hebrew@5.0/hebrew-700-normal.ttf',
|
||||
]
|
||||
|
||||
async function main() {
|
||||
await Promise.all(
|
||||
FONTS.map(async urlStr => {
|
||||
const url = new URL(urlStr)
|
||||
const res = await fetch(url)
|
||||
const font = await res.arrayBuffer()
|
||||
const filename = url.pathname
|
||||
.split('/')
|
||||
.slice(-2)
|
||||
.join('/')
|
||||
.replace(/@[\d.]+\//, '-')
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}: fetching failed for ${filename}`)
|
||||
}
|
||||
await writeFile(
|
||||
path.join(__DIRNAME, '..', 'src', 'assets', 'fonts', filename),
|
||||
Buffer.from(font),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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')}`} />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/* 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))
|
||||
}
|
||||
const isLongTitle = record ? record.name.length > 30 : false
|
||||
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: isLongTitle ? 55 : 65,
|
||||
display: 'flex',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{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>
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {readdirSync, readFileSync} from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import {fileURLToPath} from 'node:url'
|
||||
|
||||
import {AtpAgent} from '@atproto/api'
|
||||
|
||||
import {Config} from './config.js'
|
||||
|
||||
const __DIRNAME = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export type AppContextOptions = {
|
||||
cfg: Config
|
||||
appviewAgent: AtpAgent
|
||||
fonts: {name: string; data: Buffer}[]
|
||||
}
|
||||
|
||||
export class AppContext {
|
||||
cfg: Config
|
||||
appviewAgent: AtpAgent
|
||||
fonts: {name: string; data: Buffer}[]
|
||||
abortController = new AbortController()
|
||||
|
||||
constructor(private opts: AppContextOptions) {
|
||||
this.cfg = this.opts.cfg
|
||||
this.appviewAgent = this.opts.appviewAgent
|
||||
this.fonts = this.opts.fonts
|
||||
}
|
||||
|
||||
static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
|
||||
const appviewAgent = new AtpAgent({service: cfg.service.appviewUrl})
|
||||
const fontDirectory = path.join(__DIRNAME, 'assets', 'fonts')
|
||||
const fontFiles = readdirSync(fontDirectory)
|
||||
const fonts = fontFiles.map(file => {
|
||||
return {
|
||||
name: path.basename(file, path.extname(file)),
|
||||
data: readFileSync(path.join(fontDirectory, file)),
|
||||
}
|
||||
})
|
||||
return new AppContext({
|
||||
cfg,
|
||||
appviewAgent,
|
||||
fonts,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import events from 'node:events'
|
||||
import http from 'node:http'
|
||||
|
||||
import express from 'express'
|
||||
import {createHttpTerminator, HttpTerminator} from 'http-terminator'
|
||||
|
||||
import {Config} from './config.js'
|
||||
import {AppContext} from './context.js'
|
||||
import {default as routes, errorHandler} from './routes/index.js'
|
||||
|
||||
export * from './config.js'
|
||||
export * from './logger.js'
|
||||
|
||||
export class CardService {
|
||||
public server?: http.Server
|
||||
private terminator?: HttpTerminator
|
||||
|
||||
constructor(public app: express.Application, public ctx: AppContext) {}
|
||||
|
||||
static async create(cfg: Config): Promise<CardService> {
|
||||
let app = express()
|
||||
|
||||
const ctx = await AppContext.fromConfig(cfg)
|
||||
app = routes(ctx, app)
|
||||
app.use(errorHandler)
|
||||
|
||||
return new CardService(app, ctx)
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.server = this.app.listen(this.ctx.cfg.service.port)
|
||||
this.server.keepAliveTimeout = 90000
|
||||
this.terminator = createHttpTerminator({server: this.server})
|
||||
await events.once(this.server, 'listening')
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
this.ctx.abortController.abort()
|
||||
await this.terminator?.terminate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import {subsystemLogger} from '@atproto/common'
|
||||
|
||||
export const httpLogger = subsystemLogger('bskyogcard')
|
||||
export const renderLogger = subsystemLogger('bskyogcard:render')
|
||||
@@ -0,0 +1,14 @@
|
||||
import {Express} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {handler} from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get(
|
||||
'/_health',
|
||||
handler(async (_req, res) => {
|
||||
const {version} = ctx.cfg.service
|
||||
return res.send({version})
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import {Express} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {default as health} from './health.js'
|
||||
import {default as starterPack} from './starter-pack.js'
|
||||
|
||||
export * from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
app = health(ctx, app) // GET /_health
|
||||
app = starterPack(ctx, app) // GET /start/:actor/:rkey
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import assert from 'node:assert'
|
||||
|
||||
import React from 'react'
|
||||
import {AppBskyGraphDefs, AtUri} from '@atproto/api'
|
||||
import resvg from '@resvg/resvg-js'
|
||||
import {Express} from 'express'
|
||||
import satori from 'satori'
|
||||
|
||||
import {
|
||||
StarterPack,
|
||||
STARTERPACK_HEIGHT,
|
||||
STARTERPACK_WIDTH,
|
||||
} from '../components/StarterPack.js'
|
||||
import {AppContext} from '../context.js'
|
||||
import {httpLogger} from '../logger.js'
|
||||
import {loadEmojiAsSvg} from '../util.js'
|
||||
import {handler, originVerifyMiddleware} from './util.js'
|
||||
|
||||
export default function (ctx: AppContext, app: Express) {
|
||||
return app.get(
|
||||
'/start/:actor/:rkey',
|
||||
originVerifyMiddleware(ctx),
|
||||
handler(async (req, res) => {
|
||||
const {actor, rkey} = req.params
|
||||
const uri = AtUri.make(actor, 'app.bsky.graph.starterpack', rkey)
|
||||
let starterPack: AppBskyGraphDefs.StarterPackView
|
||||
try {
|
||||
const result = await ctx.appviewAgent.api.app.bsky.graph.getStarterPack(
|
||||
{starterPack: uri.toString()},
|
||||
)
|
||||
starterPack = result.data.starterPack
|
||||
} catch (err) {
|
||||
httpLogger.warn(
|
||||
{err, uri: uri.toString()},
|
||||
'could not fetch starter pack',
|
||||
)
|
||||
return res.status(404).end('not found')
|
||||
}
|
||||
const imageEntries = await Promise.all(
|
||||
[starterPack.creator]
|
||||
.concat(starterPack.listItemsSample.map(li => li.subject))
|
||||
// has avatar
|
||||
.filter(p => p.avatar)
|
||||
// no sensitive labels
|
||||
.filter(p => !p.labels.some(l => hideAvatarLabels.has(l.val)))
|
||||
.map(async p => {
|
||||
try {
|
||||
assert(p.avatar)
|
||||
const image = await getImage(p.avatar)
|
||||
return [p.did, image] as const
|
||||
} catch (err) {
|
||||
httpLogger.warn(
|
||||
{err, uri: uri.toString(), did: p.did},
|
||||
'could not fetch image',
|
||||
)
|
||||
return [p.did, null] as const
|
||||
}
|
||||
}),
|
||||
)
|
||||
const images = new Map(
|
||||
imageEntries.filter(([_, image]) => image !== null).slice(0, 7),
|
||||
)
|
||||
const svg = await satori(
|
||||
<StarterPack starterPack={starterPack} images={images} />,
|
||||
{
|
||||
fonts: ctx.fonts,
|
||||
height: STARTERPACK_HEIGHT,
|
||||
width: STARTERPACK_WIDTH,
|
||||
loadAdditionalAsset: async (code, text) => {
|
||||
if (code === 'emoji') {
|
||||
return await loadEmojiAsSvg(text)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
const output = await resvg.renderAsync(svg)
|
||||
res.statusCode = 200
|
||||
res.setHeader('content-type', 'image/png')
|
||||
res.setHeader('cdn-tag', [...images.keys()].join(','))
|
||||
return res.end(output.asPng())
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async function getImage(url: string) {
|
||||
const response = await fetch(url)
|
||||
const arrayBuf = await response.arrayBuffer() // must drain body even if it will be discarded
|
||||
if (response.status !== 200) return null
|
||||
return Buffer.from(arrayBuf)
|
||||
}
|
||||
|
||||
const hideAvatarLabels = new Set([
|
||||
'!hide',
|
||||
'!warn',
|
||||
'porn',
|
||||
'sexual',
|
||||
'nudity',
|
||||
'sexual-figurative',
|
||||
'graphic-media',
|
||||
'self-harm',
|
||||
'sensitive',
|
||||
'security',
|
||||
'impersonation',
|
||||
'scam',
|
||||
'spam',
|
||||
'misleading',
|
||||
'inauthentic',
|
||||
])
|
||||
@@ -0,0 +1,36 @@
|
||||
import {ErrorRequestHandler, Request, RequestHandler, Response} from 'express'
|
||||
|
||||
import {AppContext} from '../context.js'
|
||||
import {httpLogger} from '../logger.js'
|
||||
|
||||
export type Handler = (req: Request, res: Response) => Awaited<void>
|
||||
|
||||
export const handler = (runHandler: Handler): RequestHandler => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
await runHandler(req, res)
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function originVerifyMiddleware(ctx: AppContext): RequestHandler {
|
||||
const {originVerify} = ctx.cfg.service
|
||||
if (!originVerify) return (_req, _res, next) => next()
|
||||
return (req, res, next) => {
|
||||
const verifyHeader = req.headers['x-origin-verify']
|
||||
if (verifyHeader !== originVerify) {
|
||||
return res.status(404).end('not found')
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
|
||||
httpLogger.error({err}, 'request error')
|
||||
if (res.headersSent) {
|
||||
return next(err)
|
||||
}
|
||||
return res.status(500).end('server error')
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import twemoji from 'twemoji'
|
||||
|
||||
import {renderLogger} from './logger.js'
|
||||
|
||||
const U200D = String.fromCharCode(0x200d)
|
||||
const UFE0F_REGEXP = /\uFE0F/g
|
||||
|
||||
export async function loadEmojiAsSvg(chars: string) {
|
||||
const cached = emojiCache.get(chars)
|
||||
if (cached) return cached
|
||||
const iconCode = twemoji.convert.toCodePoint(
|
||||
chars.indexOf(U200D) < 0 ? chars.replace(UFE0F_REGEXP, '') : chars,
|
||||
)
|
||||
const res = await fetch(getEmojiUrl(iconCode))
|
||||
const body = await res.arrayBuffer()
|
||||
if (!res.ok) {
|
||||
renderLogger.warn(
|
||||
{status: res.status, err: Buffer.from(body).toString()},
|
||||
'could not fetch emoji',
|
||||
)
|
||||
return
|
||||
}
|
||||
const svg =
|
||||
'data:image/svg+xml;base64,' + Buffer.from(body).toString('base64')
|
||||
emojiCache.set(chars, svg)
|
||||
return svg
|
||||
}
|
||||
|
||||
const emojiCache = new Map<string, string>()
|
||||
|
||||
function getEmojiUrl(code: string) {
|
||||
return (
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/' +
|
||||
code.toLowerCase() +
|
||||
'.svg'
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "NodeNext",
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["./src/index.ts", "./src/bin.ts"]
|
||||
}
|
||||
@@ -24,7 +24,7 @@ Then build and copy over the big 'ol `bundle.web.js` file:
|
||||
|
||||
### Golang Daemon
|
||||
|
||||
Install golang. We are generally using v1.21+.
|
||||
Install golang. We are generally using v1.22+.
|
||||
|
||||
In this directory (`bskyweb/`):
|
||||
|
||||
|
||||
@@ -35,11 +35,17 @@ func run(args []string) {
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "appview-host",
|
||||
Usage: "method, hostname, and port of PDS instance",
|
||||
Usage: "scheme, hostname, and port of PDS instance",
|
||||
Value: "http://localhost:2584",
|
||||
// retain old PDS env var for easy transition
|
||||
EnvVars: []string{"ATP_APPVIEW_HOST", "ATP_PDS_HOST"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ogcard-host",
|
||||
Usage: "scheme, hostname, and port of ogcard service",
|
||||
Required: false,
|
||||
EnvVars: []string{"OGCARD_HOST"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-address",
|
||||
Usage: "Specify the local IP/port to bind to",
|
||||
@@ -47,6 +53,13 @@ func run(args []string) {
|
||||
Value: ":8100",
|
||||
EnvVars: []string{"HTTP_ADDRESS"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "link-host",
|
||||
Usage: "scheme, hostname, and port of link service",
|
||||
Required: false,
|
||||
Value: "",
|
||||
EnvVars: []string{"LINK_HOST"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "Enable debug mode",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
@@ -30,12 +31,23 @@ type Server struct {
|
||||
echo *echo.Echo
|
||||
httpd *http.Server
|
||||
xrpcc *xrpc.Client
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
debug bool
|
||||
httpAddress string
|
||||
appviewHost string
|
||||
ogcardHost string
|
||||
linkHost string
|
||||
}
|
||||
|
||||
func serve(cctx *cli.Context) error {
|
||||
debug := cctx.Bool("debug")
|
||||
httpAddress := cctx.String("http-address")
|
||||
appviewHost := cctx.String("appview-host")
|
||||
ogcardHost := cctx.String("ogcard-host")
|
||||
linkHost := cctx.String("link-host")
|
||||
|
||||
// Echo
|
||||
e := echo.New()
|
||||
@@ -71,6 +83,13 @@ func serve(cctx *cli.Context) error {
|
||||
server := &Server{
|
||||
echo: e,
|
||||
xrpcc: xrpcc,
|
||||
cfg: &Config{
|
||||
debug: debug,
|
||||
httpAddress: httpAddress,
|
||||
appviewHost: appviewHost,
|
||||
ogcardHost: ogcardHost,
|
||||
linkHost: linkHost,
|
||||
},
|
||||
}
|
||||
|
||||
// Create the HTTP server.
|
||||
@@ -221,6 +240,18 @@ func serve(cctx *cli.Context) error {
|
||||
e.GET("/profile/:handleOrDID/post/:rkey/liked-by", server.WebGeneric)
|
||||
e.GET("/profile/:handleOrDID/post/:rkey/reposted-by", server.WebGeneric)
|
||||
|
||||
// starter packs
|
||||
e.GET("/starter-pack/:handleOrDID/:rkey", server.WebStarterPack)
|
||||
e.GET("/start/:handleOrDID/:rkey", server.WebStarterPack)
|
||||
|
||||
if linkHost != "" {
|
||||
linkUrl, err := url.Parse(linkHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Group("/:linkId", server.LinkProxyMiddleware(linkUrl))
|
||||
}
|
||||
|
||||
// Start the server.
|
||||
log.Infof("starting server address=%s", httpAddress)
|
||||
go func() {
|
||||
@@ -292,6 +323,30 @@ func (srv *Server) Download(c echo.Context) error {
|
||||
return c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
// Handler for proxying top-level paths to link service, which ends up serving a redirect
|
||||
func (srv *Server) LinkProxyMiddleware(url *url.URL) echo.MiddlewareFunc {
|
||||
return middleware.ProxyWithConfig(
|
||||
middleware.ProxyConfig{
|
||||
Balancer: middleware.NewRoundRobinBalancer(
|
||||
[]*middleware.ProxyTarget{{URL: url}},
|
||||
),
|
||||
Skipper: func(c echo.Context) bool {
|
||||
req := c.Request()
|
||||
if req.Method == "GET" &&
|
||||
strings.LastIndex(strings.TrimRight(req.URL.Path, "/"), "/") == 0 && // top-level path
|
||||
!strings.HasPrefix(req.URL.Path, "/_") { // e.g. /_health endpoint
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
RetryCount: 2,
|
||||
ErrorHandler: func(c echo.Context, err error) error {
|
||||
return c.Redirect(302, "/")
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// handler for endpoint that have no specific server-side handling
|
||||
func (srv *Server) WebGeneric(c echo.Context) error {
|
||||
data := pongo2.Context{}
|
||||
@@ -377,6 +432,45 @@ func (srv *Server) WebPost(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "post.html", data)
|
||||
}
|
||||
|
||||
func (srv *Server) WebStarterPack(c echo.Context) error {
|
||||
req := c.Request()
|
||||
ctx := req.Context()
|
||||
data := pongo2.Context{}
|
||||
data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path)
|
||||
// sanity check arguments. don't 4xx, just let app handle if not expected format
|
||||
rkeyParam := c.Param("rkey")
|
||||
rkey, err := syntax.ParseRecordKey(rkeyParam)
|
||||
if err != nil {
|
||||
log.Errorf("bad rkey: %v", err)
|
||||
return c.Render(http.StatusOK, "starterpack.html", data)
|
||||
}
|
||||
handleOrDIDParam := c.Param("handleOrDID")
|
||||
handleOrDID, err := syntax.ParseAtIdentifier(handleOrDIDParam)
|
||||
if err != nil {
|
||||
log.Errorf("bad identifier: %v", err)
|
||||
return c.Render(http.StatusOK, "starterpack.html", data)
|
||||
}
|
||||
identifier := handleOrDID.Normalize().String()
|
||||
starterPackURI := fmt.Sprintf("at://%s/app.bsky.graph.starterpack/%s", identifier, rkey)
|
||||
spv, err := appbsky.GraphGetStarterPack(ctx, srv.xrpcc, starterPackURI)
|
||||
if err != nil {
|
||||
log.Errorf("failed to fetch starter pack view for: %s\t%v", starterPackURI, err)
|
||||
return c.Render(http.StatusOK, "starterpack.html", data)
|
||||
}
|
||||
if spv.StarterPack == nil || spv.StarterPack.Record == nil {
|
||||
return c.Render(http.StatusOK, "starterpack.html", data)
|
||||
}
|
||||
rec, ok := spv.StarterPack.Record.Val.(*appbsky.GraphStarterpack)
|
||||
if !ok {
|
||||
return c.Render(http.StatusOK, "starterpack.html", data)
|
||||
}
|
||||
data["title"] = rec.Name
|
||||
if srv.cfg.ogcardHost != "" {
|
||||
data["imgThumbUrl"] = fmt.Sprintf("%s/start/%s/%s", srv.cfg.ogcardHost, identifier, rkey)
|
||||
}
|
||||
return c.Render(http.StatusOK, "starterpack.html", data)
|
||||
}
|
||||
|
||||
func (srv *Server) WebProfile(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
data := pongo2.Context{}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
module github.com/bluesky-social/social-app/bskyweb
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.4
|
||||
|
||||
require (
|
||||
github.com/bluesky-social/indigo v0.0.0-20231216010655-ad730a7da4f5
|
||||
github.com/bluesky-social/indigo v0.0.0-20240624223826-fd66f93ce141
|
||||
github.com/flosch/pongo2/v6 v6.0.0
|
||||
github.com/ipfs/go-log v1.0.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
@@ -19,7 +21,7 @@ require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -48,7 +50,7 @@ require (
|
||||
github.com/jbenet/goprocess v0.1.4 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/labstack/gommon v0.4.1 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
@@ -58,10 +60,9 @@ require (
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.18 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||
@@ -69,6 +70,7 @@ require (
|
||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/orandin/slog-gorm v1.3.2 // indirect
|
||||
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
|
||||
github.com/prometheus/client_golang v1.17.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
@@ -79,28 +81,28 @@ require (
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/whyrusleeping/cbor-gen v0.0.0-20230923211252-36a87e1ba72f // indirect
|
||||
github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302 // indirect
|
||||
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
|
||||
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gorm.io/driver/postgres v1.5.0 // indirect
|
||||
gorm.io/driver/sqlite v1.5.4 // indirect
|
||||
gorm.io/gorm v1.25.5 // indirect
|
||||
gorm.io/driver/postgres v1.5.7 // indirect
|
||||
gorm.io/driver/sqlite v1.5.5 // indirect
|
||||
gorm.io/gorm v1.25.9 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
)
|
||||
|
||||
@@ -2,8 +2,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bluesky-social/indigo v0.0.0-20231216010655-ad730a7da4f5 h1:Zk1c+mxCYH6G/vLL0+9lO2Eci4OT3AFy73qPWa9auDM=
|
||||
github.com/bluesky-social/indigo v0.0.0-20231216010655-ad730a7da4f5/go.mod h1:a8cPbqDkRX+aPwJnXF7kAi3PF26hYiR4w5H8624MB7k=
|
||||
github.com/bluesky-social/indigo v0.0.0-20240624223826-fd66f93ce141 h1:F3ZceqS82fFfVV3ychWRLNDRFaFlbOwrPic0qJiUVqw=
|
||||
github.com/bluesky-social/indigo v0.0.0-20240624223826-fd66f93ce141/go.mod h1:dBIOGhsiK0rgEETnxiGiuEyrSx6DKxEotHIPbiKD6WU=
|
||||
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=
|
||||
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
@@ -11,7 +11,6 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -23,8 +22,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
|
||||
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
@@ -35,7 +34,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -60,7 +58,6 @@ github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
|
||||
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
|
||||
github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=
|
||||
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
|
||||
github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I=
|
||||
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
||||
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
|
||||
github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=
|
||||
@@ -88,10 +85,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
|
||||
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
|
||||
@@ -109,11 +104,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
|
||||
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -143,37 +136,28 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
|
||||
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
|
||||
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
|
||||
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
||||
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
|
||||
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
|
||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
|
||||
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
|
||||
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
|
||||
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/orandin/slog-gorm v1.3.2 h1:C0lKDQPAx/pF+8K2HL7bdShPwOEJpPM0Bn80zTzxU1g=
|
||||
github.com/orandin/slog-gorm v1.3.2/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -188,7 +172,6 @@ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGy
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -213,9 +196,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
@@ -225,8 +208,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
|
||||
github.com/whyrusleeping/cbor-gen v0.0.0-20230923211252-36a87e1ba72f h1:SBuSxXJL0/ZJMtTxbXZgHZkThl9dNrzyaNhlyaqscRo=
|
||||
github.com/whyrusleeping/cbor-gen v0.0.0-20230923211252-36a87e1ba72f/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ=
|
||||
github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302 h1:MhInbXe4SzcImAKktUvWBCWZgcw6MYf5NfumTj1BhAw=
|
||||
github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
|
||||
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic=
|
||||
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
@@ -239,8 +222,8 @@ gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRyS
|
||||
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
|
||||
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
|
||||
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
@@ -265,14 +248,12 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -290,8 +271,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -316,8 +297,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -365,13 +346,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
|
||||
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"applinks": {
|
||||
"apps": [],
|
||||
"details": [
|
||||
{
|
||||
"appID": "B3LX46C5HS.xyz.blueskyweb.app",
|
||||
@@ -9,5 +8,8 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"appclips": {
|
||||
"apps": ["B3LX46C5HS.xyz.blueskyweb.app.AppClip"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block html_head_extra -%}
|
||||
<meta name="apple-itunes-app" content="app-id=xyz.blueskyweb.app, app-clip-bundle-id=xyz.blueskyweb.app.AppClip, app-clip-display=card">
|
||||
{%- if requestURI %}
|
||||
<meta property="og:url" content="{{ requestURI }}">
|
||||
{% endif -%}
|
||||
{%- if imgThumbUrl %}
|
||||
<meta property="og:image" content="{{ imgThumbUrl }}">
|
||||
<meta property="twitter:image" content="{{ imgThumbUrl }}">
|
||||
{%- else -%}
|
||||
<meta property="og:image" content="https://bsky.app/static/social-card-default-gradient.png" />
|
||||
<meta property="twitter:image" content="https://bsky.app/static/social-card-default-gradient.png" />
|
||||
{% endif -%}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
{%- if title %}
|
||||
<meta property="og:title" content="{{ title }}" />
|
||||
<meta name="twitter:title" content="{{ title }}" />
|
||||
{%- else -%}
|
||||
<meta property="og:title" content="Bluesky" />
|
||||
<meta name="twitter:title" content="Bluesky" />
|
||||
{% endif -%}
|
||||
<meta name="description" content="Join the conversation" />
|
||||
<meta name="og:description" content="Join the conversation" />
|
||||
<meta name="twitter:description" content="Join the conversation" />
|
||||
{%- endblock %}
|
||||
@@ -0,0 +1,32 @@
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
var controller: ViewController?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
let window = UIWindow()
|
||||
self.window = UIWindow()
|
||||
|
||||
let controller = ViewController(window: window)
|
||||
self.controller = controller
|
||||
|
||||
window.rootViewController = self.controller
|
||||
window.makeKeyAndVisible()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
self.controller?.handleURL(url: url)
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
if let incomingURL = userActivity.webpageURL {
|
||||
self.controller?.handleURL(url: incomingURL)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 463 KiB |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "App-Icon-1024x1024@1x.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import UIKit
|
||||
import WebKit
|
||||
import StoreKit
|
||||
|
||||
class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate {
|
||||
let defaults = UserDefaults(suiteName: "group.app.bsky")
|
||||
|
||||
var window: UIWindow
|
||||
var webView: WKWebView?
|
||||
|
||||
var prevUrl: URL?
|
||||
var starterPackUrl: URL?
|
||||
|
||||
init(window: UIWindow) {
|
||||
self.window = window
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let contentController = WKUserContentController()
|
||||
contentController.add(self, name: "onMessage")
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.userContentController = contentController
|
||||
|
||||
let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
webView.contentMode = .scaleToFill
|
||||
webView.navigationDelegate = self
|
||||
self.view.addSubview(webView)
|
||||
self.webView = webView
|
||||
}
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
guard let response = message.body as? String,
|
||||
let data = response.data(using: .utf8),
|
||||
let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else {
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.action {
|
||||
case .present:
|
||||
guard let url = self.starterPackUrl else {
|
||||
return
|
||||
}
|
||||
|
||||
self.presentAppStoreOverlay()
|
||||
defaults?.setValue(url.absoluteString, forKey: "starterPackUri")
|
||||
|
||||
case .store:
|
||||
guard let keyToStoreAs = payload.keyToStoreAs, let jsonToStore = payload.jsonToStore else {
|
||||
return
|
||||
}
|
||||
|
||||
self.defaults?.setValue(jsonToStore, forKey: keyToStoreAs)
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
|
||||
// Detect when we land on the right URL. This is incase of a short link opening the app clip
|
||||
guard let url = navigationAction.request.url else {
|
||||
return .allow
|
||||
}
|
||||
|
||||
// Store the previous one to compare later, but only set starterPackUrl when we find the right one
|
||||
prevUrl = url
|
||||
// pathComponents starts with "/" as the first component, then each path name. so...
|
||||
// ["/", "start", "name", "rkey"]
|
||||
if url.pathComponents.count == 4,
|
||||
url.pathComponents[1] == "start" {
|
||||
self.starterPackUrl = url
|
||||
}
|
||||
|
||||
return .allow
|
||||
}
|
||||
|
||||
func handleURL(url: URL) {
|
||||
let urlString = "\(url.absoluteString)?clip=true"
|
||||
if let url = URL(string: urlString) {
|
||||
self.webView?.load(URLRequest(url: url))
|
||||
}
|
||||
}
|
||||
|
||||
func presentAppStoreOverlay() {
|
||||
guard let windowScene = self.window.windowScene else {
|
||||
return
|
||||
}
|
||||
|
||||
let configuration = SKOverlay.AppClipConfiguration(position: .bottomRaised)
|
||||
let overlay = SKOverlay(configuration: configuration)
|
||||
|
||||
overlay.present(in: windowScene)
|
||||
}
|
||||
|
||||
func getHost(_ url: URL?) -> String? {
|
||||
if #available(iOS 16.0, *) {
|
||||
return url?.host()
|
||||
} else {
|
||||
return url?.host
|
||||
}
|
||||
}
|
||||
|
||||
func getQuery(_ url: URL?) -> String? {
|
||||
if #available(iOS 16.0, *) {
|
||||
return url?.query()
|
||||
} else {
|
||||
return url?.query
|
||||
}
|
||||
}
|
||||
|
||||
func urlMatchesPrevious(_ url: URL?) -> Bool {
|
||||
if #available(iOS 16.0, *) {
|
||||
return url?.query() == prevUrl?.query() && url?.host() == prevUrl?.host() && url?.query() == prevUrl?.query()
|
||||
} else {
|
||||
return url?.query == prevUrl?.query && url?.host == prevUrl?.host && url?.query == prevUrl?.query
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WebViewActionPayload: Decodable {
|
||||
enum Action: String, Decodable {
|
||||
case present, store
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let keyToStoreAs: String?
|
||||
let jsonToStore: String?
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
group = 'expo.modules.blueskyswissarmy'
|
||||
version = '0.6.0'
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
|
||||
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
||||
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
||||
// Most of the time, you may like to manage the Android SDK versions yourself.
|
||||
def useManagedAndroidSdkVersions = false
|
||||
if (useManagedAndroidSdkVersions) {
|
||||
useDefaultAndroidSdkVersions()
|
||||
} else {
|
||||
buildscript {
|
||||
// Simple helper that allows the root project to override versions declared by this library.
|
||||
ext.safeExtGet = { prop, fallback ->
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
}
|
||||
project.android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "expo.modules.blueskyswissarmy"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.6.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.android.installreferrer:installreferrer:2.2")
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest>
|
||||
</manifest>
|
||||
@@ -0,0 +1,10 @@
|
||||
package expo.modules.blueskyswissarmy.deviceprefs
|
||||
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
|
||||
class ExpoBlueskyDevicePrefsModule : Module() {
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("ExpoBlueskyDevicePrefs")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package expo.modules.blueskyswissarmy.referrer
|
||||
|
||||
import android.util.Log
|
||||
import com.android.installreferrer.api.InstallReferrerClient
|
||||
import com.android.installreferrer.api.InstallReferrerStateListener
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
import expo.modules.kotlin.Promise
|
||||
|
||||
class ExpoBlueskyReferrerModule : Module() {
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("ExpoBlueskyReferrer")
|
||||
|
||||
AsyncFunction("getGooglePlayReferrerInfoAsync") { promise: Promise ->
|
||||
val referrerClient = InstallReferrerClient.newBuilder(appContext.reactContext).build()
|
||||
referrerClient.startConnection(object : InstallReferrerStateListener {
|
||||
override fun onInstallReferrerSetupFinished(responseCode: Int) {
|
||||
if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
|
||||
Log.d("ExpoGooglePlayReferrer", "Successfully retrieved referrer info.")
|
||||
|
||||
val response = referrerClient.installReferrer
|
||||
Log.d("ExpoGooglePlayReferrer", "Install referrer: ${response.installReferrer}")
|
||||
|
||||
promise.resolve(
|
||||
mapOf(
|
||||
"installReferrer" to response.installReferrer,
|
||||
"clickTimestamp" to response.referrerClickTimestampSeconds,
|
||||
"installTimestamp" to response.installBeginTimestampSeconds
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Log.d("ExpoGooglePlayReferrer", "Failed to get referrer info. Unknown error.")
|
||||
promise.reject(
|
||||
"ERR_GOOGLE_PLAY_REFERRER_UNKNOWN",
|
||||
"Failed to get referrer info",
|
||||
Exception("Failed to get referrer info")
|
||||
)
|
||||
}
|
||||
referrerClient.endConnection()
|
||||
}
|
||||
|
||||
override fun onInstallReferrerServiceDisconnected() {
|
||||
Log.d("ExpoGooglePlayReferrer", "Failed to get referrer info. Service disconnected.")
|
||||
referrerClient.endConnection()
|
||||
promise.reject(
|
||||
"ERR_GOOGLE_PLAY_REFERRER_DISCONNECTED",
|
||||
"Failed to get referrer info",
|
||||
Exception("Failed to get referrer info")
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"platforms": ["ios", "tvos", "android", "web"],
|
||||
"ios": {
|
||||
"modules": ["ExpoBlueskyDevicePrefsModule", "ExpoBlueskyReferrerModule"]
|
||||
},
|
||||
"android": {
|
||||
"modules": [
|
||||
"expo.modules.blueskyswissarmy.deviceprefs.ExpoBlueskyDevicePrefsModule",
|
||||
"expo.modules.blueskyswissarmy.referrer.ExpoBlueskyReferrerModule"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as DevicePrefs from './src/DevicePrefs'
|
||||
import * as Referrer from './src/Referrer'
|
||||
|
||||
export {DevicePrefs, Referrer}
|
||||
@@ -0,0 +1,23 @@
|
||||
import ExpoModulesCore
|
||||
|
||||
public class ExpoBlueskyDevicePrefsModule: Module {
|
||||
func getDefaults(_ useAppGroup: Bool) -> UserDefaults? {
|
||||
if useAppGroup {
|
||||
return UserDefaults(suiteName: "group.app.bsky")
|
||||
} else {
|
||||
return UserDefaults.standard
|
||||
}
|
||||
}
|
||||
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("ExpoBlueskyDevicePrefs")
|
||||
|
||||
AsyncFunction("getStringValueAsync") { (key: String, useAppGroup: Bool) in
|
||||
return self.getDefaults(useAppGroup)?.string(forKey: key)
|
||||
}
|
||||
|
||||
AsyncFunction("setStringValueAsync") { (key: String, value: String?, useAppGroup: Bool) in
|
||||
self.getDefaults(useAppGroup)?.setValue(value, forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ExpoBlueskySwissArmy'
|
||||
s.version = '1.0.0'
|
||||
s.summary = 'A collection of native tools for Bluesky'
|
||||
s.description = 'A collection of native tools for Bluesky'
|
||||
s.author = ''
|
||||
s.homepage = 'https://github.com/bluesky-social/social-app'
|
||||
s.platforms = { :ios => '13.4', :tvos => '13.4' }
|
||||
s.source = { git: '' }
|
||||
s.static_framework = true
|
||||
|
||||
s.dependency 'ExpoModulesCore'
|
||||
|
||||
# Swift/Objective-C compatibility
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
||||
}
|
||||
|
||||
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
import ExpoModulesCore
|
||||
|
||||
public class ExpoBlueskyReferrerModule: Module {
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("ExpoBlueskyReferrer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {requireNativeModule} from 'expo-modules-core'
|
||||
|
||||
const NativeModule = requireNativeModule('ExpoBlueskyDevicePrefs')
|
||||
|
||||
export function getStringValueAsync(
|
||||
key: string,
|
||||
useAppGroup?: boolean,
|
||||
): Promise<string | null> {
|
||||
return NativeModule.getStringValueAsync(key, useAppGroup)
|
||||
}
|
||||
|
||||
export function setStringValueAsync(
|
||||
key: string,
|
||||
value: string | null,
|
||||
useAppGroup?: boolean,
|
||||
): Promise<void> {
|
||||
return NativeModule.setStringValueAsync(key, value, useAppGroup)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {NotImplementedError} from '../NotImplemented'
|
||||
|
||||
export function getStringValueAsync(
|
||||
key: string,
|
||||
useAppGroup?: boolean,
|
||||
): Promise<string | null> {
|
||||
throw new NotImplementedError({key, useAppGroup})
|
||||
}
|
||||
|
||||
export function setStringValueAsync(
|
||||
key: string,
|
||||
value: string | null,
|
||||
useAppGroup?: boolean,
|
||||
): Promise<string | null> {
|
||||
throw new NotImplementedError({key, value, useAppGroup})
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {Platform} from 'react-native'
|
||||
|
||||
export class NotImplementedError extends Error {
|
||||
constructor(params = {}) {
|
||||
if (__DEV__) {
|
||||
const caller = new Error().stack?.split('\n')[2]
|
||||
super(
|
||||
`Not implemented on ${Platform.OS}. Given params: ${JSON.stringify(
|
||||
params,
|
||||
)} ${caller}`,
|
||||
)
|
||||
} else {
|
||||
super('Not implemented')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import {requireNativeModule} from 'expo'
|
||||
|
||||
import {GooglePlayReferrerInfo} from './types'
|
||||
|
||||
export const NativeModule = requireNativeModule('ExpoBlueskyReferrer')
|
||||
|
||||
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo> {
|
||||
return NativeModule.getGooglePlayReferrerInfoAsync()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import {NotImplementedError} from '../NotImplemented'
|
||||
import {GooglePlayReferrerInfo} from './types'
|
||||
|
||||
// @ts-ignore throws
|
||||
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo> {
|
||||
throw new NotImplementedError()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export type GooglePlayReferrerInfo =
|
||||
| {
|
||||
installReferrer?: string
|
||||
clickTimestamp?: number
|
||||
installTimestamp?: number
|
||||
}
|
||||
| undefined
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.87.0",
|
||||
"version": "1.88.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -49,7 +49,7 @@
|
||||
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.12.20",
|
||||
"@atproto/api": "^0.12.23",
|
||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||
@@ -136,6 +136,7 @@
|
||||
"expo-system-ui": "~3.0.4",
|
||||
"expo-task-manager": "~11.8.1",
|
||||
"expo-updates": "~0.25.14",
|
||||
"expo-video": "^1.1.10",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"history": "^5.3.0",
|
||||
@@ -164,10 +165,13 @@
|
||||
"react-avatar-editor": "^13.0.0",
|
||||
"react-compiler-runtime": "file:./lib/react-compiler-runtime",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-keyed-flatten-children": "^3.0.0",
|
||||
"react-native": "0.74.1",
|
||||
"react-native-compressor": "^1.8.24",
|
||||
"react-native-date-picker": "^4.4.2",
|
||||
"react-native-drawer-layout": "^4.0.0-alpha.3",
|
||||
"react-native-fetch-api": "^3.0.0",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "~2.16.2",
|
||||
"react-native-get-random-values": "~1.11.0",
|
||||
@@ -177,6 +181,7 @@
|
||||
"react-native-pager-view": "6.2.3",
|
||||
"react-native-picker-select": "^9.1.3",
|
||||
"react-native-progress": "bluesky-social/react-native-progress",
|
||||
"react-native-qrcode-styled": "^0.3.1",
|
||||
"react-native-reanimated": "^3.11.0",
|
||||
"react-native-root-siblings": "^4.1.1",
|
||||
"react-native-safe-area-context": "4.10.1",
|
||||
@@ -190,12 +195,15 @@
|
||||
"react-native-web-webview": "^1.0.2",
|
||||
"react-native-webview": "13.10.2",
|
||||
"react-responsive": "^9.0.2",
|
||||
"react-server-dom-webpack": "19.0.0-canary-adb717393-20240411",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
"rn-tourguide": "bluesky-social/rn-tourguide",
|
||||
"sentry-expo": "~7.0.1",
|
||||
"statsig-react-native-expo": "^4.6.1",
|
||||
"tippy.js": "^6.3.7",
|
||||
"tlds": "^1.234.0",
|
||||
"web-streams-polyfill": "3.1.1",
|
||||
"zeego": "^1.6.2",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
@@ -205,7 +213,7 @@
|
||||
"@babel/preset-env": "^7.20.0",
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@did-plc/server": "^0.0.1",
|
||||
"@expo/config-plugins": "7.8.0",
|
||||
"@expo/config-plugins": "8.0.4",
|
||||
"@expo/prebuild-config": "6.7.0",
|
||||
"@lingui/cli": "^4.5.0",
|
||||
"@lingui/macro": "^4.5.0",
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
|
||||
index dcb23c5..9f5ee0b 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
|
||||
@@ -128,7 +128,7 @@ var chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -195,7 +195,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -227,9 +227,9 @@ var chunkMap = new Map();
|
||||
* of chunks that come from Flight and may not be known to the webpack runtime
|
||||
*/
|
||||
|
||||
-var webpackGetChunkFilename = __webpack_require__.u;
|
||||
+var webpackGetChunkFilename = __bsky_require__.u;
|
||||
|
||||
-__webpack_require__.u = function (chunkId) {
|
||||
+__bsky_require__.u = function (chunkId) {
|
||||
var flightChunk = chunkMap.get(chunkId);
|
||||
|
||||
if (flightChunk !== undefined) {
|
||||
@@ -241,7 +241,7 @@ __webpack_require__.u = function (chunkId) {
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
chunkMap.set(chunkId, filename);
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
var ReactDOMSharedInternals = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js
|
||||
index 6ad0195..a400d00 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js
|
||||
@@ -81,7 +81,7 @@ const chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- const promise = __webpack_require__(id);
|
||||
+ const promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -148,7 +148,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- let moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ let moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -180,9 +180,9 @@ const chunkMap = new Map();
|
||||
* of chunks that come from Flight and may not be known to the webpack runtime
|
||||
*/
|
||||
|
||||
-const webpackGetChunkFilename = __webpack_require__.u;
|
||||
+const webpackGetChunkFilename = __bsky_require__.u;
|
||||
|
||||
-__webpack_require__.u = function (chunkId) {
|
||||
+__bsky_require__.u = function (chunkId) {
|
||||
const flightChunk = chunkMap.get(chunkId);
|
||||
|
||||
if (flightChunk !== undefined) {
|
||||
@@ -194,7 +194,7 @@ __webpack_require__.u = function (chunkId) {
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
chunkMap.set(chunkId, filename);
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
const ReactDOMSharedInternals = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.min.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.min.js
|
||||
index 8573fab..1b34e95 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.min.js
|
||||
@@ -8,8 +8,8 @@
|
||||
LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
'use strict';var u=require("react-dom"),v={stream:!0};function z(a,b){if(a){var c=a[b[0]];if(a=c[b[2]])c=a.name;else{a=c["*"];if(!a)throw Error('Could not find the module "'+b[0]+'" in the React SSR Manifest. This is probably a bug in the React Server Components bundler.');c=b[2]}return 4===b.length?[a.id,a.chunks,c,1]:[a.id,a.chunks,c]}return b}var A=new Map;
|
||||
-function B(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function C(){}
|
||||
-function D(a){for(var b=a[1],c=[],e=0;e<b.length;){var m=b[e++],k=b[e++],g=A.get(m);void 0===g?(E.set(m,k),k=__webpack_chunk_load__(m),c.push(k),g=A.set.bind(A,m,null),k.then(g,C),A.set(m,k)):null!==g&&c.push(g)}return 4===a.length?0===c.length?B(a[0]):Promise.all(c).then(function(){return B(a[0])}):0<c.length?Promise.all(c):null}var E=new Map,aa=__webpack_require__.u;__webpack_require__.u=function(a){var b=E.get(a);return void 0!==b?b:aa(a)};
|
||||
+function B(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function C(){}
|
||||
+function D(a){for(var b=a[1],c=[],e=0;e<b.length;){var m=b[e++],k=b[e++],g=A.get(m);void 0===g?(E.set(m,k),k=__bsky_chunk_load__(m),c.push(k),g=A.set.bind(A,m,null),k.then(g,C),A.set(m,k)):null!==g&&c.push(g)}return 4===a.length?0===c.length?B(a[0]):Promise.all(c).then(function(){return B(a[0])}):0<c.length?Promise.all(c):null}var E=new Map,aa=__bsky_require__.u;__bsky_require__.u=function(a){var b=E.get(a);return void 0!==b?b:aa(a)};
|
||||
var ba=u.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,G=Symbol.for("react.element"),H=Symbol.for("react.lazy"),I=Symbol.iterator;function ca(a){if(null===a||"object"!==typeof a)return null;a=I&&a[I]||a["@@iterator"];return"function"===typeof a?a:null}var da=Array.isArray,J=Object.getPrototypeOf;function K(a,b){var c=a.length;a.push(b);return c}var ea=Object.prototype,L=new WeakMap;
|
||||
function fa(a){return Number.isFinite(a)?0===a&&-Infinity===1/a?"$-0":a:Infinity===a?"$Infinity":-Infinity===a?"$-Infinity":"$NaN"}
|
||||
function ha(a,b,c,e,m){function k(q,d){if(null===d)return null;if("object"===typeof d){switch(d.$$typeof){case G:if(void 0===c)throw Error("React Element cannot be passed to Server Functions from the Client without a temporary reference set. Pass a TemporaryReferenceSet to the options.");return"$T"+K(c,d).toString(16);case H:q=d._payload;var f=d._init;null===l&&(l=new FormData);h++;try{var p=f(q),n=g++,t=JSON.stringify(p,k);l.append(b+n,t);return"$"+n.toString(16)}catch(r){if("object"===typeof r&&
|
||||
@@ -21,7 +21,7 @@ function M(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}M.
|
||||
function ma(a){switch(a.status){case "resolved_model":N(a);break;case "resolved_module":O(a)}switch(a.status){case "fulfilled":return a.value;case "pending":case "blocked":case "cyclic":throw a;default:throw a.reason;}}function P(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}function Q(a,b,c){switch(a.status){case "fulfilled":P(b,a.value);break;case "pending":case "blocked":case "cyclic":a.value=b;a.reason=c;break;case "rejected":c&&P(c,a.reason)}}
|
||||
function R(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&P(c,b)}}function S(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.value,e=a.reason;a.status="resolved_module";a.value=b;null!==c&&(O(a),Q(a,c,e))}}var T=null,U=null;
|
||||
function N(a){var b=T,c=U;T=a;U=null;var e=a.value;a.status="cyclic";a.value=null;a.reason=null;try{var m=JSON.parse(e,a._response._fromJSON);if(null!==U&&0<U.deps)U.value=m,a.status="blocked",a.value=null,a.reason=null;else{var k=a.value;a.status="fulfilled";a.value=m;null!==k&&P(k,m)}}catch(g){a.status="rejected",a.reason=g}finally{T=b,U=c}}
|
||||
-function O(a){try{var b=a.value,c=__webpack_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var e="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=e}catch(m){a.status="rejected",a.reason=m}}function V(a,b){a._chunks.forEach(function(c){"pending"===c.status&&R(c,b)})}function W(a,b){var c=a._chunks,e=c.get(b);e||(e=new M("pending",null,null,a),c.set(b,e));return e}
|
||||
+function O(a){try{var b=a.value,c=__bsky_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var e="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=e}catch(m){a.status="rejected",a.reason=m}}function V(a,b){a._chunks.forEach(function(c){"pending"===c.status&&R(c,b)})}function W(a,b){var c=a._chunks,e=c.get(b);e||(e=new M("pending",null,null,a),c.set(b,e));return e}
|
||||
function na(a,b,c,e,m,k){if(U){var g=U;e||g.deps++}else g=U={deps:e?0:1,value:null};return function(h){b[c]=k(m,h);""===c&&null===g.value&&(g.value=b[c]);g.deps--;0===g.deps&&"blocked"===a.status&&(h=a.value,a.status="fulfilled",a.value=g.value,null!==h&&P(h,g.value))}}function oa(a){return function(b){return R(a,b)}}
|
||||
function pa(a,b){function c(){var m=Array.prototype.slice.call(arguments),k=b.bound;return k?"fulfilled"===k.status?e(b.id,k.value.concat(m)):Promise.resolve(k).then(function(g){return e(b.id,g.concat(m))}):e(b.id,m)}var e=a._callServer;L.set(c,b);return c}
|
||||
function X(a,b,c,e,m){b=W(a,b);switch(b.status){case "resolved_model":N(b);break;case "resolved_module":O(b)}switch(b.status){case "fulfilled":return m(a,b.value);case "pending":case "blocked":case "cyclic":var k=T;b.then(na(k,c,e,"cyclic"===b.status,a,m),oa(k));return null;default:throw b.reason;}}function qa(a,b){return new Map(b)}function ra(a,b){return new Set(b)}function sa(a,b){a=new FormData;for(var c=0;c<b.length;c++)a.append(b[c][0],b[c][1]);return a}function ta(a,b){return b}
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js
|
||||
index 993d7cd..59a6c24 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js
|
||||
@@ -139,7 +139,7 @@ var chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -206,7 +206,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -233,7 +233,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
function prepareDestinationWithChunks(moduleLoading, // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js
|
||||
index 83b0367..f5b0e0b 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js
|
||||
@@ -91,7 +91,7 @@ const chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- const promise = __webpack_require__(id);
|
||||
+ const promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -158,7 +158,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- let moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ let moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -185,7 +185,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
function prepareDestinationWithChunks(moduleLoading, // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.min.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.min.js
|
||||
index c00ccd0..661d3a2 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.min.js
|
||||
@@ -8,8 +8,8 @@
|
||||
LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
'use strict';var aa=require("react-dom"),ba={stream:!0};function ca(a,b){if(a){var c=a[b[0]];if(a=c[b[2]])c=a.name;else{a=c["*"];if(!a)throw Error('Could not find the module "'+b[0]+'" in the React SSR Manifest. This is probably a bug in the React Server Components bundler.');c=b[2]}return 4===b.length?[a.id,a.chunks,c,1]:[a.id,a.chunks,c]}return b}var u=new Map;
|
||||
-function v(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function da(){}
|
||||
-function ea(a){for(var b=a[1],c=[],d=0;d<b.length;){var f=b[d++];b[d++];var g=u.get(f);if(void 0===g){g=__webpack_chunk_load__(f);c.push(g);var l=u.set.bind(u,f,null);g.then(l,da);u.set(f,g)}else null!==g&&c.push(g)}return 4===a.length?0===c.length?v(a[0]):Promise.all(c).then(function(){return v(a[0])}):0<c.length?Promise.all(c):null}
|
||||
+function v(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function da(){}
|
||||
+function ea(a){for(var b=a[1],c=[],d=0;d<b.length;){var f=b[d++];b[d++];var g=u.get(f);if(void 0===g){g=__bsky_chunk_load__(f);c.push(g);var l=u.set.bind(u,f,null);g.then(l,da);u.set(f,g)}else null!==g&&c.push(g)}return 4===a.length?0===c.length?v(a[0]):Promise.all(c).then(function(){return v(a[0])}):0<c.length?Promise.all(c):null}
|
||||
function fa(a,b,c){if(null!==a)for(var d=1;d<b.length;d+=2){var f=c,g=w.d,l=g.X,k=a.prefix+b[d];var m=a.crossOrigin;m="string"===typeof m?"use-credentials"===m?m:"":void 0;l.call(g,k,{crossOrigin:m,nonce:f})}}var w=aa.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,A=Symbol.for("react.element"),B=Symbol.for("react.lazy"),C=Symbol.iterator;function ha(a){if(null===a||"object"!==typeof a)return null;a=C&&a[C]||a["@@iterator"];return"function"===typeof a?a:null}var ia=Array.isArray,D=Object.getPrototypeOf;
|
||||
function E(a,b){var c=a.length;a.push(b);return c}var ja=Object.prototype,F=new WeakMap;function ka(a){return Number.isFinite(a)?0===a&&-Infinity===1/a?"$-0":a:Infinity===a?"$Infinity":-Infinity===a?"$-Infinity":"$NaN"}
|
||||
function H(a,b,c,d,f){function g(q,e){if(null===e)return null;if("object"===typeof e){switch(e.$$typeof){case A:if(void 0===c)throw Error("React Element cannot be passed to Server Functions from the Client without a temporary reference set. Pass a TemporaryReferenceSet to the options.");return"$T"+E(c,e).toString(16);case B:q=e._payload;var h=e._init;null===m&&(m=new FormData);k++;try{var p=h(q),n=l++,t=JSON.stringify(p,g);m.append(b+n,t);return"$"+n.toString(16)}catch(r){if("object"===typeof r&&
|
||||
@@ -26,7 +26,7 @@ function M(a,b,c,d){this.status=a;this.value=b;this.reason=c;this._response=d}M.
|
||||
function ta(a){switch(a.status){case "resolved_model":N(a);break;case "resolved_module":O(a)}switch(a.status){case "fulfilled":return a.value;case "pending":case "blocked":case "cyclic":throw a;default:throw a.reason;}}function P(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}function Q(a,b,c){switch(a.status){case "fulfilled":P(b,a.value);break;case "pending":case "blocked":case "cyclic":a.value=b;a.reason=c;break;case "rejected":c&&P(c,a.reason)}}
|
||||
function R(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&P(c,b)}}function S(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.value,d=a.reason;a.status="resolved_module";a.value=b;null!==c&&(O(a),Q(a,c,d))}}var T=null,U=null;
|
||||
function N(a){var b=T,c=U;T=a;U=null;var d=a.value;a.status="cyclic";a.value=null;a.reason=null;try{var f=JSON.parse(d,a._response._fromJSON);if(null!==U&&0<U.deps)U.value=f,a.status="blocked",a.value=null,a.reason=null;else{var g=a.value;a.status="fulfilled";a.value=f;null!==g&&P(g,f)}}catch(l){a.status="rejected",a.reason=l}finally{T=b,U=c}}
|
||||
-function O(a){try{var b=a.value,c=__webpack_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var d="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=d}catch(f){a.status="rejected",a.reason=f}}function V(a,b){a._chunks.forEach(function(c){"pending"===c.status&&R(c,b)})}function W(a,b){var c=a._chunks,d=c.get(b);d||(d=new M("pending",null,null,a),c.set(b,d));return d}
|
||||
+function O(a){try{var b=a.value,c=__bsky_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var d="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=d}catch(f){a.status="rejected",a.reason=f}}function V(a,b){a._chunks.forEach(function(c){"pending"===c.status&&R(c,b)})}function W(a,b){var c=a._chunks,d=c.get(b);d||(d=new M("pending",null,null,a),c.set(b,d));return d}
|
||||
function ua(a,b,c,d,f,g){if(U){var l=U;d||l.deps++}else l=U={deps:d?0:1,value:null};return function(k){b[c]=g(f,k);""===c&&null===l.value&&(l.value=b[c]);l.deps--;0===l.deps&&"blocked"===a.status&&(k=a.value,a.status="fulfilled",a.value=l.value,null!==k&&P(k,l.value))}}function va(a){return function(b){return R(a,b)}}
|
||||
function wa(a,b){function c(){var f=Array.prototype.slice.call(arguments),g=b.bound;return g?"fulfilled"===g.status?d(b.id,g.value.concat(f)):Promise.resolve(g).then(function(l){return d(b.id,l.concat(f))}):d(b.id,f)}var d=a._callServer;K(c,b,a._encodeFormAction);return c}
|
||||
function X(a,b,c,d,f){b=W(a,b);switch(b.status){case "resolved_model":N(b);break;case "resolved_module":O(b)}switch(b.status){case "fulfilled":return f(a,b.value);case "pending":case "blocked":case "cyclic":var g=T;b.then(ua(g,c,d,"cyclic"===b.status,a,f),va(g));return null;default:throw b.reason;}}function xa(a,b){return new Map(b)}function ya(a,b){return new Set(b)}function za(a,b){a=new FormData;for(var c=0;c<b.length;c++)a.append(b[c][0],b[c][1]);return a}function Aa(a,b){return b}
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js
|
||||
index 6a03153..f9143a7 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js
|
||||
@@ -140,7 +140,7 @@ var chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -207,7 +207,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -234,7 +234,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
function prepareDestinationWithChunks(moduleLoading, // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js
|
||||
index 62abbfd..f5f1630 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js
|
||||
@@ -92,7 +92,7 @@ const chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- const promise = __webpack_require__(id);
|
||||
+ const promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -159,7 +159,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- let moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ let moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -186,7 +186,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
function prepareDestinationWithChunks(moduleLoading, // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.min.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.min.js
|
||||
index 6c4162e..d26ec84 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.min.js
|
||||
@@ -8,8 +8,8 @@
|
||||
LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
'use strict';var r=require("util"),t=require("react-dom"),aa={stream:!0};function ba(a,b){if(a){var c=a[b[0]];if(a=c[b[2]])c=a.name;else{a=c["*"];if(!a)throw Error('Could not find the module "'+b[0]+'" in the React SSR Manifest. This is probably a bug in the React Server Components bundler.');c=b[2]}return 4===b.length?[a.id,a.chunks,c,1]:[a.id,a.chunks,c]}return b}var u=new Map;
|
||||
-function x(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function ca(){}
|
||||
-function da(a){for(var b=a[1],c=[],d=0;d<b.length;){var f=b[d++];b[d++];var g=u.get(f);if(void 0===g){g=__webpack_chunk_load__(f);c.push(g);var h=u.set.bind(u,f,null);g.then(h,ca);u.set(f,g)}else null!==g&&c.push(g)}return 4===a.length?0===c.length?x(a[0]):Promise.all(c).then(function(){return x(a[0])}):0<c.length?Promise.all(c):null}
|
||||
+function x(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function ca(){}
|
||||
+function da(a){for(var b=a[1],c=[],d=0;d<b.length;){var f=b[d++];b[d++];var g=u.get(f);if(void 0===g){g=__bsky_chunk_load__(f);c.push(g);var h=u.set.bind(u,f,null);g.then(h,ca);u.set(f,g)}else null!==g&&c.push(g)}return 4===a.length?0===c.length?x(a[0]):Promise.all(c).then(function(){return x(a[0])}):0<c.length?Promise.all(c):null}
|
||||
function ea(a,b,c){if(null!==a)for(var d=1;d<b.length;d+=2){var f=c,g=y.d,h=g.X,l=a.prefix+b[d];var e=a.crossOrigin;e="string"===typeof e?"use-credentials"===e?e:"":void 0;h.call(g,l,{crossOrigin:e,nonce:f})}}var y=t.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,z=Symbol.for("react.element"),A=Symbol.for("react.lazy"),B=Symbol.iterator;function fa(a){if(null===a||"object"!==typeof a)return null;a=B&&a[B]||a["@@iterator"];return"function"===typeof a?a:null}var ha=Array.isArray,D=Object.getPrototypeOf;
|
||||
function E(a,b){var c=a.length;a.push(b);return c}var ia=Object.prototype,F=new WeakMap;function ja(a){return Number.isFinite(a)?0===a&&-Infinity===1/a?"$-0":a:Infinity===a?"$Infinity":-Infinity===a?"$-Infinity":"$NaN"}
|
||||
function ka(a,b,c,d,f){function g(n,k){if(null===k)return null;if("object"===typeof k){switch(k.$$typeof){case z:if(void 0===c)throw Error("React Element cannot be passed to Server Functions from the Client without a temporary reference set. Pass a TemporaryReferenceSet to the options.");return"$T"+E(c,k).toString(16);case A:n=k._payload;var I=k._init;null===e&&(e=new FormData);l++;try{var m=I(n),p=h++,v=JSON.stringify(m,g);e.append(b+p,v);return"$"+p.toString(16)}catch(q){if("object"===typeof q&&
|
||||
@@ -26,7 +26,7 @@ function L(a,b,c,d){this.status=a;this.value=b;this.reason=c;this._response=d}L.
|
||||
function ta(a){switch(a.status){case "resolved_model":M(a);break;case "resolved_module":N(a)}switch(a.status){case "fulfilled":return a.value;case "pending":case "blocked":case "cyclic":throw a;default:throw a.reason;}}function Q(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}function R(a,b,c){switch(a.status){case "fulfilled":Q(b,a.value);break;case "pending":case "blocked":case "cyclic":a.value=b;a.reason=c;break;case "rejected":c&&Q(c,a.reason)}}
|
||||
function S(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&Q(c,b)}}function T(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.value,d=a.reason;a.status="resolved_module";a.value=b;null!==c&&(N(a),R(a,c,d))}}var U=null,V=null;
|
||||
function M(a){var b=U,c=V;U=a;V=null;var d=a.value;a.status="cyclic";a.value=null;a.reason=null;try{var f=JSON.parse(d,a._response._fromJSON);if(null!==V&&0<V.deps)V.value=f,a.status="blocked",a.value=null,a.reason=null;else{var g=a.value;a.status="fulfilled";a.value=f;null!==g&&Q(g,f)}}catch(h){a.status="rejected",a.reason=h}finally{U=b,V=c}}
|
||||
-function N(a){try{var b=a.value,c=__webpack_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var d="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=d}catch(f){a.status="rejected",a.reason=f}}function W(a,b){a._chunks.forEach(function(c){"pending"===c.status&&S(c,b)})}function X(a,b){var c=a._chunks,d=c.get(b);d||(d=new L("pending",null,null,a),c.set(b,d));return d}
|
||||
+function N(a){try{var b=a.value,c=__bsky_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var d="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=d}catch(f){a.status="rejected",a.reason=f}}function W(a,b){a._chunks.forEach(function(c){"pending"===c.status&&S(c,b)})}function X(a,b){var c=a._chunks,d=c.get(b);d||(d=new L("pending",null,null,a),c.set(b,d));return d}
|
||||
function ua(a,b,c,d,f,g){if(V){var h=V;d||h.deps++}else h=V={deps:d?0:1,value:null};return function(l){b[c]=g(f,l);""===c&&null===h.value&&(h.value=b[c]);h.deps--;0===h.deps&&"blocked"===a.status&&(l=a.value,a.status="fulfilled",a.value=h.value,null!==l&&Q(l,h.value))}}function va(a){return function(b){return S(a,b)}}
|
||||
function wa(a,b){function c(){var f=Array.prototype.slice.call(arguments),g=b.bound;return g?"fulfilled"===g.status?d(b.id,g.value.concat(f)):Promise.resolve(g).then(function(h){return d(b.id,h.concat(f))}):d(b.id,f)}var d=a._callServer;J(c,b,a._encodeFormAction);return c}
|
||||
function Y(a,b,c,d,f){b=X(a,b);switch(b.status){case "resolved_model":M(b);break;case "resolved_module":N(b)}switch(b.status){case "fulfilled":return f(a,b.value);case "pending":case "blocked":case "cyclic":var g=U;b.then(ua(g,c,d,"cyclic"===b.status,a,f),va(g));return null;default:throw b.reason;}}function xa(a,b){return new Map(b)}function ya(a,b){return new Set(b)}function za(a,b){a=new FormData;for(var c=0;c<b.length;c++)a.append(b[c][0],b[c][1]);return a}function Aa(a,b){return b}
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js
|
||||
index 17c270d..74f4ef3 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js
|
||||
@@ -3301,7 +3301,7 @@ var chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -3368,7 +3368,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -3400,9 +3400,9 @@ var chunkMap = new Map();
|
||||
* of chunks that come from Flight and may not be known to the webpack runtime
|
||||
*/
|
||||
|
||||
-var webpackGetChunkFilename = __webpack_require__.u;
|
||||
+var webpackGetChunkFilename = __bsky_require__.u;
|
||||
|
||||
-__webpack_require__.u = function (chunkId) {
|
||||
+__bsky_require__.u = function (chunkId) {
|
||||
var flightChunk = chunkMap.get(chunkId);
|
||||
|
||||
if (flightChunk !== undefined) {
|
||||
@@ -3414,7 +3414,7 @@ __webpack_require__.u = function (chunkId) {
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
chunkMap.set(chunkId, filename);
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js
|
||||
index da256f7..040d06d 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js
|
||||
@@ -2650,7 +2650,7 @@ const chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- const promise = __webpack_require__(id);
|
||||
+ const promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -2717,7 +2717,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- let moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ let moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -2749,9 +2749,9 @@ const chunkMap = new Map();
|
||||
* of chunks that come from Flight and may not be known to the webpack runtime
|
||||
*/
|
||||
|
||||
-const webpackGetChunkFilename = __webpack_require__.u;
|
||||
+const webpackGetChunkFilename = __bsky_require__.u;
|
||||
|
||||
-__webpack_require__.u = function (chunkId) {
|
||||
+__bsky_require__.u = function (chunkId) {
|
||||
const flightChunk = chunkMap.get(chunkId);
|
||||
|
||||
if (flightChunk !== undefined) {
|
||||
@@ -2763,7 +2763,7 @@ __webpack_require__.u = function (chunkId) {
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
chunkMap.set(chunkId, filename);
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js
|
||||
index e33cbce..9cd2ef6 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js
|
||||
@@ -60,9 +60,9 @@ function gb(a){var b=K.H;K.H=Pa;var c=x;F=x=a;try{var e=a.pingedTasks;a.pingedTa
|
||||
function P(a,b){l=new Uint8Array(2048);n=0;try{for(var c=a.completedImportChunks,e=0;e<c.length;e++)a.pendingChunks--,p(b,c[e]);c.splice(0,e);var d=a.completedHintChunks;for(e=0;e<d.length;e++)p(b,d[e]);d.splice(0,e);var g=a.completedRegularChunks;for(e=0;e<g.length;e++)a.pendingChunks--,p(b,g[e]);g.splice(0,e);var f=a.completedErrorChunks;for(e=0;e<f.length;e++)a.pendingChunks--,p(b,f[e]);f.splice(0,e)}finally{a.flushScheduled=!1,l&&0<n&&(b.enqueue(new Uint8Array(l.buffer,0,n)),l=null,n=0)}0===a.pendingChunks&&
|
||||
b.close()}function lb(a,b){try{var c=a.abortableTasks;if(0<c.size){a.pendingChunks++;var e=a.nextChunkId++,d=void 0===b?Error("The render was aborted by the server without a reason."):b,g=N(a,d);O(a,e,g,d);c.forEach(function(f){f.status=3;var h=T(e);f=hb(a,f.id,h);a.completedErrorChunks.push(f)});c.clear()}null!==a.destination&&P(a,a.destination)}catch(f){N(a,f),kb(a,f)}}
|
||||
function mb(a,b){var c="",e=a[b];if(e)c=e.name;else{var d=b.lastIndexOf("#");-1!==d&&(c=b.slice(d+1),e=a[b.slice(0,d)]);if(!e)throw Error('Could not find the module "'+b+'" in the React Server Manifest. This is probably a bug in the React Server Components bundler.');}return[e.id,e.chunks,c]}var nb=new Map;
|
||||
-function ob(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function pb(){}
|
||||
-function qb(a){for(var b=a[1],c=[],e=0;e<b.length;){var d=b[e++],g=b[e++],f=nb.get(d);void 0===f?(rb.set(d,g),g=__webpack_chunk_load__(d),c.push(g),f=nb.set.bind(nb,d,null),g.then(f,pb),nb.set(d,g)):null!==f&&c.push(f)}return 4===a.length?0===c.length?ob(a[0]):Promise.all(c).then(function(){return ob(a[0])}):0<c.length?Promise.all(c):null}
|
||||
-function W(a){var b=__webpack_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}var rb=new Map,sb=__webpack_require__.u;__webpack_require__.u=function(a){var b=rb.get(a);return void 0!==b?b:sb(a)};function tb(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}tb.prototype=Object.create(Promise.prototype);
|
||||
+function ob(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function pb(){}
|
||||
+function qb(a){for(var b=a[1],c=[],e=0;e<b.length;){var d=b[e++],g=b[e++],f=nb.get(d);void 0===f?(rb.set(d,g),g=__bsky_chunk_load__(d),c.push(g),f=nb.set.bind(nb,d,null),g.then(f,pb),nb.set(d,g)):null!==f&&c.push(f)}return 4===a.length?0===c.length?ob(a[0]):Promise.all(c).then(function(){return ob(a[0])}):0<c.length?Promise.all(c):null}
|
||||
+function W(a){var b=__bsky_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}var rb=new Map,sb=__bsky_require__.u;__bsky_require__.u=function(a){var b=rb.get(a);return void 0!==b?b:sb(a)};function tb(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}tb.prototype=Object.create(Promise.prototype);
|
||||
tb.prototype.then=function(a,b){switch(this.status){case "resolved_model":ub(this)}switch(this.status){case "fulfilled":a(this.value);break;case "pending":case "blocked":a&&(null===this.value&&(this.value=[]),this.value.push(a));b&&(null===this.reason&&(this.reason=[]),this.reason.push(b));break;default:b(this.reason)}};function vb(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}
|
||||
function wb(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&vb(c,b)}}function xb(a,b,c,e,d,g){var f=mb(a._bundlerConfig,b);a=qb(f);if(c)c=Promise.all([c,a]).then(function(h){h=h[0];var k=W(f);return k.bind.apply(k,[null].concat(h))});else if(a)c=Promise.resolve(a).then(function(){return W(f)});else return W(f);c.then(yb(e,d,g),zb(e));return null}var X=null,Y=null;
|
||||
function ub(a){var b=X,c=Y;X=a;Y=null;try{var e=JSON.parse(a.value,a._response._fromJSON);null!==Y&&0<Y.deps?(Y.value=e,a.status="blocked",a.value=null,a.reason=null):(a.status="fulfilled",a.value=e)}catch(d){a.status="rejected",a.reason=d}finally{X=b,Y=c}}function Ab(a,b){a._chunks.forEach(function(c){"pending"===c.status&&wb(c,b)})}
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js
|
||||
index bca3958..08e4c00 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js
|
||||
@@ -3324,7 +3324,7 @@ var chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -3391,7 +3391,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -3418,7 +3418,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js
|
||||
index 03ef137..457d5d8 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js
|
||||
@@ -2673,7 +2673,7 @@ const chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- const promise = __webpack_require__(id);
|
||||
+ const promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -2740,7 +2740,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- let moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ let moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -2767,7 +2767,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js
|
||||
index a2a2408..f249bf6 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js
|
||||
@@ -62,9 +62,9 @@ function R(a,b){m=new Uint8Array(2048);n=0;try{for(var c=a.completedImportChunks
|
||||
b.close()}function pb(a){a.flushScheduled=null!==a.destination;B?setTimeout(function(){return ua.run(a,kb,a)},0):setTimeout(function(){return kb(a)},0)}function cb(a){if(!1===a.flushScheduled&&0===a.pingedTasks.length&&null!==a.destination){var b=a.destination;a.flushScheduled=!0;setTimeout(function(){return R(a,b)},0)}}
|
||||
function qb(a,b){try{var c=a.abortableTasks;if(0<c.size){a.pendingChunks++;var e=a.nextChunkId++,d=void 0===b?Error("The render was aborted by the server without a reason."):b,g=P(a,d);Q(a,e,g,d);c.forEach(function(f){f.status=3;var h=T(e);f=lb(a,f.id,h);a.completedErrorChunks.push(f)});c.clear()}null!==a.destination&&R(a,a.destination)}catch(f){P(a,f),ob(a,f)}}
|
||||
function rb(a,b){var c="",e=a[b];if(e)c=e.name;else{var d=b.lastIndexOf("#");-1!==d&&(c=b.slice(d+1),e=a[b.slice(0,d)]);if(!e)throw Error('Could not find the module "'+b+'" in the React Server Manifest. This is probably a bug in the React Server Components bundler.');}return[e.id,e.chunks,c]}var sb=new Map;
|
||||
-function tb(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function ub(){}
|
||||
-function vb(a){for(var b=a[1],c=[],e=0;e<b.length;){var d=b[e++];b[e++];var g=sb.get(d);if(void 0===g){g=__webpack_chunk_load__(d);c.push(g);var f=sb.set.bind(sb,d,null);g.then(f,ub);sb.set(d,g)}else null!==g&&c.push(g)}return 4===a.length?0===c.length?tb(a[0]):Promise.all(c).then(function(){return tb(a[0])}):0<c.length?Promise.all(c):null}
|
||||
-function W(a){var b=__webpack_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}function wb(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}wb.prototype=Object.create(Promise.prototype);
|
||||
+function tb(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function ub(){}
|
||||
+function vb(a){for(var b=a[1],c=[],e=0;e<b.length;){var d=b[e++];b[e++];var g=sb.get(d);if(void 0===g){g=__bsky_chunk_load__(d);c.push(g);var f=sb.set.bind(sb,d,null);g.then(f,ub);sb.set(d,g)}else null!==g&&c.push(g)}return 4===a.length?0===c.length?tb(a[0]):Promise.all(c).then(function(){return tb(a[0])}):0<c.length?Promise.all(c):null}
|
||||
+function W(a){var b=__bsky_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}function wb(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}wb.prototype=Object.create(Promise.prototype);
|
||||
wb.prototype.then=function(a,b){switch(this.status){case "resolved_model":xb(this)}switch(this.status){case "fulfilled":a(this.value);break;case "pending":case "blocked":a&&(null===this.value&&(this.value=[]),this.value.push(a));b&&(null===this.reason&&(this.reason=[]),this.reason.push(b));break;default:b(this.reason)}};function yb(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}
|
||||
function zb(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&yb(c,b)}}function Ab(a,b,c,e,d,g){var f=rb(a._bundlerConfig,b);a=vb(f);if(c)c=Promise.all([c,a]).then(function(h){h=h[0];var k=W(f);return k.bind.apply(k,[null].concat(h))});else if(a)c=Promise.resolve(a).then(function(){return W(f)});else return W(f);c.then(Bb(e,d,g),Cb(e));return null}var X=null,Y=null;
|
||||
function xb(a){var b=X,c=Y;X=a;Y=null;try{var e=JSON.parse(a.value,a._response._fromJSON);null!==Y&&0<Y.deps?(Y.value=e,a.status="blocked",a.value=null,a.reason=null):(a.status="fulfilled",a.value=e)}catch(d){a.status="rejected",a.reason=d}finally{X=b,Y=c}}function Db(a,b){a._chunks.forEach(function(c){"pending"===c.status&&zb(c,b)})}
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
|
||||
index 8b8345e..fa6ba3a 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
|
||||
@@ -3377,7 +3377,7 @@ var chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -3444,7 +3444,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -3471,7 +3471,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js
|
||||
index a643b5a..0bbba48 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js
|
||||
@@ -2742,7 +2742,7 @@ const chunkCache = new Map();
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- const promise = __webpack_require__(id);
|
||||
+ const promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -2809,7 +2809,7 @@ function preloadModule(metadata) {
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- let moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ let moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -2836,7 +2836,7 @@ function requireModule(metadata) {
|
||||
}
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js
|
||||
index 8a88b6e..b4f74ce 100644
|
||||
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js
|
||||
@@ -65,9 +65,9 @@ null;d++;break}g.splice(0,d)}finally{a.flushScheduled=!1,l&&0<m&&b.write(l.subar
|
||||
function sb(a,b){if(1===a.status)a.status=2,b.destroy(a.fatalError);else if(2!==a.status&&null===a.destination){a.destination=b;try{R(a,b)}catch(c){P(a,c),qb(a,c)}}}
|
||||
function tb(a,b){try{var c=a.abortableTasks;if(0<c.size){a.pendingChunks++;var d=a.nextChunkId++,e=void 0===b?Error("The render was aborted by the server without a reason."):b,f=P(a,e);Q(a,d,f,e);c.forEach(function(g){g.status=3;var k=T(d);g=nb(a,g.id,k);a.completedErrorChunks.push(g)});c.clear()}null!==a.destination&&R(a,a.destination)}catch(g){P(a,g),qb(a,g)}}
|
||||
function ub(a,b){var c="",d=a[b];if(d)c=d.name;else{var e=b.lastIndexOf("#");-1!==e&&(c=b.slice(e+1),d=a[b.slice(0,e)]);if(!d)throw Error('Could not find the module "'+b+'" in the React Server Manifest. This is probably a bug in the React Server Components bundler.');}return[d.id,d.chunks,c]}var vb=new Map;
|
||||
-function wb(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function xb(){}
|
||||
-function yb(a){for(var b=a[1],c=[],d=0;d<b.length;){var e=b[d++];b[d++];var f=vb.get(e);if(void 0===f){f=__webpack_chunk_load__(e);c.push(f);var g=vb.set.bind(vb,e,null);f.then(g,xb);vb.set(e,f)}else null!==f&&c.push(f)}return 4===a.length?0===c.length?wb(a[0]):Promise.all(c).then(function(){return wb(a[0])}):0<c.length?Promise.all(c):null}
|
||||
-function W(a){var b=__webpack_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}function zb(a,b,c,d){this.status=a;this.value=b;this.reason=c;this._response=d}zb.prototype=Object.create(Promise.prototype);
|
||||
+function wb(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function xb(){}
|
||||
+function yb(a){for(var b=a[1],c=[],d=0;d<b.length;){var e=b[d++];b[d++];var f=vb.get(e);if(void 0===f){f=__bsky_chunk_load__(e);c.push(f);var g=vb.set.bind(vb,e,null);f.then(g,xb);vb.set(e,f)}else null!==f&&c.push(f)}return 4===a.length?0===c.length?wb(a[0]):Promise.all(c).then(function(){return wb(a[0])}):0<c.length?Promise.all(c):null}
|
||||
+function W(a){var b=__bsky_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}function zb(a,b,c,d){this.status=a;this.value=b;this.reason=c;this._response=d}zb.prototype=Object.create(Promise.prototype);
|
||||
zb.prototype.then=function(a,b){switch(this.status){case "resolved_model":Ab(this)}switch(this.status){case "fulfilled":a(this.value);break;case "pending":case "blocked":a&&(null===this.value&&(this.value=[]),this.value.push(a));b&&(null===this.reason&&(this.reason=[]),this.reason.push(b));break;default:b(this.reason)}};function Bb(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}
|
||||
function Cb(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&Bb(c,b)}}function Db(a,b,c,d,e,f){var g=ub(a._bundlerConfig,b);a=yb(g);if(c)c=Promise.all([c,a]).then(function(k){k=k[0];var h=W(g);return h.bind.apply(h,[null].concat(k))});else if(a)c=Promise.resolve(a).then(function(){return W(g)});else return W(g);c.then(Eb(d,e,f),Fb(d));return null}var X=null,Y=null;
|
||||
function Ab(a){var b=X,c=Y;X=a;Y=null;try{var d=JSON.parse(a.value,a._response._fromJSON);null!==Y&&0<Y.deps?(Y.value=d,a.status="blocked",a.value=null,a.reason=null):(a.status="fulfilled",a.value=d)}catch(e){a.status="rejected",a.reason=e}finally{X=b,Y=c}}function Gb(a,b){a._chunks.forEach(function(c){"pending"===c.status&&Cb(c,b)})}
|
||||
diff --git a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.development.js b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.development.js
|
||||
index 0f6aa55..baf15f0 100644
|
||||
--- a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.development.js
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -192,7 +192,7 @@
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -224,9 +224,9 @@
|
||||
* of chunks that come from Flight and may not be known to the webpack runtime
|
||||
*/
|
||||
|
||||
- var webpackGetChunkFilename = __webpack_require__.u;
|
||||
+ var webpackGetChunkFilename = __bsky_require__.u;
|
||||
|
||||
- __webpack_require__.u = function (chunkId) {
|
||||
+ __bsky_require__.u = function (chunkId) {
|
||||
var flightChunk = chunkMap.get(chunkId);
|
||||
|
||||
if (flightChunk !== undefined) {
|
||||
@@ -238,7 +238,7 @@
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
chunkMap.set(chunkId, filename);
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
var ReactDOMSharedInternals = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
||||
diff --git a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.production.min.js b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.production.min.js
|
||||
index 3d11f8f..7fc4655 100644
|
||||
--- a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-client.browser.production.min.js
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
'use strict';(function(){(function(t,y){"object"===typeof exports&&"undefined"!==typeof module?y(exports,require("react-dom")):"function"===typeof define&&define.amd?define(["exports","react-dom"],y):(t="undefined"!==typeof globalThis?globalThis:t||self,y(t.ReactServerDOMClient={},t.ReactDOM))})(this,function(t,y){function X(a,b){if(a){var c=a[b[0]];if(a=c[b[2]])c=a.name;else{a=c["*"];if(!a)throw Error('Could not find the module "'+b[0]+'" in the React SSR Manifest. This is probably a bug in the React Server Components bundler.');
|
||||
-c=b[2]}return 4===b.length?[a.id,a.chunks,c,1]:[a.id,a.chunks,c]}return b}function O(a){var b=__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function Y(){}function Z(a){for(var b=a[1],c=[],e=0;e<b.length;){var m=b[e++],k=b[e++],h=E.get(m);void 0===h?(P.set(m,k),k=__webpack_chunk_load__(m),c.push(k),h=E.set.bind(E,m,null),k.then(h,Y),E.set(m,k)):null!==
|
||||
+c=b[2]}return 4===b.length?[a.id,a.chunks,c,1]:[a.id,a.chunks,c]}return b}function O(a){var b=__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function Y(){}function Z(a){for(var b=a[1],c=[],e=0;e<b.length;){var m=b[e++],k=b[e++],h=E.get(m);void 0===h?(P.set(m,k),k=__bsky_chunk_load__(m),c.push(k),h=E.set.bind(E,m,null),k.then(h,Y),E.set(m,k)):null!==
|
||||
h&&c.push(h)}return 4===a.length?0===c.length?O(a[0]):Promise.all(c).then(function(){return O(a[0])}):0<c.length?Promise.all(c):null}function aa(a){if(null===a||"object"!==typeof a)return null;a=Q&&a[Q]||a["@@iterator"];return"function"===typeof a?a:null}function F(a,b){var c=a.length;a.push(b);return c}function ba(a){return Number.isFinite(a)?0===a&&-Infinity===1/a?"$-0":a:Infinity===a?"$Infinity":-Infinity===a?"$-Infinity":"$NaN"}function ca(a,b,c,e,m){function k(q,d){if(null===d)return null;if("object"===
|
||||
typeof d){switch(d.$$typeof){case G:if(void 0===c)throw Error("React Element cannot be passed to Server Functions from the Client without a temporary reference set. Pass a TemporaryReferenceSet to the options.");return"$T"+F(c,d).toString(16);case R:q=d._payload;var f=d._init;null===l&&(l=new FormData);g++;try{var p=f(q),n=h++,u=JSON.stringify(p,k);l.append(b+n,u);return"$"+n.toString(16)}catch(r){if("object"===typeof r&&null!==r&&"function"===typeof r.then){g++;var z=h++;p=function(){try{var A=JSON.stringify(d,
|
||||
k),H=l;H.append(b+z,A);g--;0===g&&e(H)}catch(da){m(da)}};r.then(p,p);return"$"+z.toString(16)}m(r);return null}finally{g--}}if("function"===typeof d.then){null===l&&(l=new FormData);g++;var x=h++;d.then(function(r){try{var A=JSON.stringify(r,k);r=l;r.append(b+x,A);g--;0===g&&e(r)}catch(H){m(H)}},function(r){m(r)});return"$@"+x.toString(16)}if(ea(d))return d;if(d instanceof FormData){null===l&&(l=new FormData);var fa=l;p=h++;var ha=b+p+"_";d.forEach(function(r,A){fa.append(ha+A,r)});return"$K"+p.toString(16)}if(d instanceof
|
||||
@@ -18,7 +18,7 @@ typeof d){if("Z"===d[d.length-1]&&this[q]instanceof Date)return"$D"+d;p="$"===d[
|
||||
return"$T"+F(c,d).toString(16)}if("symbol"===typeof d){if(void 0===c)throw Error("Symbols cannot be passed to a Server Function without a temporary reference set. Pass a TemporaryReferenceSet to the options.");return"$T"+F(c,d).toString(16)}if("bigint"===typeof d)return"$n"+d.toString(10);throw Error("Type "+typeof d+" is not supported as an argument to a Server Function.");}var h=1,g=0,l=null;a=JSON.stringify(a,k);null===l?e(a):(l.set(b+"0",a),0===g&&e(l))}function ja(a,b,c){M.set(a,b)}function w(a,
|
||||
b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}function ka(a){switch(a.status){case "resolved_model":I(a);break;case "resolved_module":J(a)}switch(a.status){case "fulfilled":return a.value;case "pending":case "blocked":case "cyclic":throw a;default:throw a.reason;}}function B(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}function T(a,b,c){switch(a.status){case "fulfilled":B(b,a.value);break;case "pending":case "blocked":case "cyclic":a.value=b;a.reason=c;break;case "rejected":c&&B(c,
|
||||
a.reason)}}function K(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&B(c,b)}}function U(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.value,e=a.reason;a.status="resolved_module";a.value=b;null!==c&&(J(a),T(a,c,e))}}function I(a){var b=L,c=v;L=a;v=null;var e=a.value;a.status="cyclic";a.value=null;a.reason=null;try{var m=JSON.parse(e,a._response._fromJSON);if(null!==v&&0<v.deps)v.value=m,a.status="blocked",a.value=null,a.reason=
|
||||
-null;else{var k=a.value;a.status="fulfilled";a.value=m;null!==k&&B(k,m)}}catch(h){a.status="rejected",a.reason=h}finally{L=b,v=c}}function J(a){try{var b=a.value,c=__webpack_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var e="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=e}catch(m){a.status="rejected",a.reason=m}}function N(a,b){a._chunks.forEach(function(c){"pending"===c.status&&K(c,b)})}
|
||||
+null;else{var k=a.value;a.status="fulfilled";a.value=m;null!==k&&B(k,m)}}catch(h){a.status="rejected",a.reason=h}finally{L=b,v=c}}function J(a){try{var b=a.value,c=__bsky_require__(b[0]);if(4===b.length&&"function"===typeof c.then)if("fulfilled"===c.status)c=c.value;else throw c.reason;var e="*"===b[2]?c:""===b[2]?c.__esModule?c.default:c:c[b[2]];a.status="fulfilled";a.value=e}catch(m){a.status="rejected",a.reason=m}}function N(a,b){a._chunks.forEach(function(c){"pending"===c.status&&K(c,b)})}
|
||||
function C(a,b){var c=a._chunks,e=c.get(b);e||(e=new w("pending",null,null,a),c.set(b,e));return e}function la(a,b,c,e,m,k){if(v){var h=v;e||h.deps++}else h=v={deps:e?0:1,value:null};return function(g){b[c]=k(m,g);""===c&&null===h.value&&(h.value=b[c]);h.deps--;0===h.deps&&"blocked"===a.status&&(g=a.value,a.status="fulfilled",a.value=h.value,null!==g&&B(g,h.value))}}function ma(a){return function(b){return K(a,b)}}function na(a,b){var c=a._callServer;a=function(){var e=Array.prototype.slice.call(arguments),
|
||||
m=b.bound;return m?"fulfilled"===m.status?c(b.id,m.value.concat(e)):Promise.resolve(m).then(function(k){return c(b.id,k.concat(e))}):c(b.id,e)};M.set(a,b);return a}function D(a,b,c,e,m){b=C(a,b);switch(b.status){case "resolved_model":I(b);break;case "resolved_module":J(b)}switch(b.status){case "fulfilled":return m(a,b.value);case "pending":case "blocked":case "cyclic":var k=L;b.then(la(k,c,e,"cyclic"===b.status,a,m),ma(k));return null;default:throw b.reason;}}function oa(a,b){return new Map(b)}function pa(a,
|
||||
b){return new Set(b)}function qa(a,b){a=new FormData;for(var c=0;c<b.length;c++)a.append(b[c][0],b[c][1]);return a}function ra(a,b){return b}function sa(a,b,c,e){if("$"===e[0]){if("$"===e)return G;switch(e[1]){case "$":return e.slice(1);case "L":return b=parseInt(e.slice(2),16),a=C(a,b),{$$typeof:R,_payload:a,_init:ka};case "@":if(2===e.length)return new Promise(function(){});b=parseInt(e.slice(2),16);return C(a,b);case "S":return Symbol.for(e.slice(2));case "F":return e=parseInt(e.slice(2),16),D(a,
|
||||
@@ -30,6 +30,6 @@ f=a._rowLength;k=a._buffer;for(var p=h.length;g<p;){var n=-1;switch(l){case 0:n=
|
||||
q,d);break;case 72:q=d[0];d=d.slice(1);f=JSON.parse(d,f._fromJSON);d=xa.d;switch(q){case "D":d.D(f);break;case "C":"string"===typeof f?d.C(f):d.C(f[0],f[1]);break;case "L":q=f[0];g=f[1];3===f.length?d.L(q,g,f[2]):d.L(q,g);break;case "m":"string"===typeof f?d.m(f):d.m(f[0],f[1]);break;case "X":"string"===typeof f?d.X(f):d.X(f[0],f[1]);break;case "S":"string"===typeof f?d.S(f):d.S(f[0],0===f[1]?void 0:f[1],3===f.length?f[2]:void 0);break;case "M":"string"===typeof f?d.M(f):d.M(f[0],f[1])}break;case 69:d=
|
||||
JSON.parse(d);g=d.digest;d=Error("An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.");d.stack="Error: "+d.message;d.digest=g;g=f._chunks;(u=g.get(q))?K(u,d):g.set(q,new w("rejected",null,d,f));break;case 84:f._chunks.set(q,new w("fulfilled",d,null,f));break;case 68:case 87:throw Error("Failed to read a RSC payload created by a development version of React on the server while using a production version on the client. Always use matching versions on the server and the client.");
|
||||
default:g=f._chunks,(u=g.get(q))?(f=u,q=d,"pending"===f.status&&(d=f.value,g=f.reason,f.status="resolved_model",f.value=q,null!==d&&(I(f),T(f,d,g)))):g.set(q,new w("resolved_model",d,null,f))}g=n;3===l&&g++;f=q=d=l=0;k.length=0}else{h=new Uint8Array(h.buffer,u,h.byteLength-g);k.push(h);f-=h.byteLength;break}}a._rowState=l;a._rowID=q;a._rowTag=d;a._rowLength=f;return m.read().then(c).catch(e)}}function e(k){N(a,k)}var m=b.getReader();m.read().then(c).catch(e)}var wa={stream:!0},E=new Map,P=new Map,
|
||||
-ya=__webpack_require__.u;__webpack_require__.u=function(a){var b=P.get(a);return void 0!==b?b:ya(a)};var xa=y.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,G=Symbol.for("react.element"),R=Symbol.for("react.lazy"),Q=Symbol.iterator,ea=Array.isArray,S=Object.getPrototypeOf,ia=Object.prototype,M=new WeakMap;w.prototype=Object.create(Promise.prototype);w.prototype.then=function(a,b){switch(this.status){case "resolved_model":I(this);break;case "resolved_module":J(this)}switch(this.status){case "fulfilled":a(this.value);
|
||||
+ya=__bsky_require__.u;__bsky_require__.u=function(a){var b=P.get(a);return void 0!==b?b:ya(a)};var xa=y.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,G=Symbol.for("react.element"),R=Symbol.for("react.lazy"),Q=Symbol.iterator,ea=Array.isArray,S=Object.getPrototypeOf,ia=Object.prototype,M=new WeakMap;w.prototype=Object.create(Promise.prototype);w.prototype.then=function(a,b){switch(this.status){case "resolved_model":I(this);break;case "resolved_module":J(this)}switch(this.status){case "fulfilled":a(this.value);
|
||||
break;case "pending":case "blocked":case "cyclic":a&&(null===this.value&&(this.value=[]),this.value.push(a));b&&(null===this.reason&&(this.reason=[]),this.reason.push(b));break;default:b(this.reason)}};var L=null,v=null;t.createFromFetch=function(a,b){var c=V(b);a.then(function(e){W(c,e.body)},function(e){N(c,e)});return C(c,0)};t.createFromReadableStream=function(a,b){b=V(b);W(b,a);return C(b,0)};t.createServerReference=function(a,b,c){c=function(){var e=Array.prototype.slice.call(arguments);return b(a,
|
||||
e)};ja(c,{id:a,bound:null});return c};t.createTemporaryReferenceSet=function(){return[]};t.encodeReply=function(a,b){return new Promise(function(c,e){ca(a,"",b&&b.temporaryReferences?b.temporaryReferences:void 0,c,e)})}})})();
|
||||
diff --git a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js
|
||||
index f85071d..0fd6534 100644
|
||||
--- a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js
|
||||
+++ b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js
|
||||
@@ -3298,7 +3298,7 @@
|
||||
|
||||
function requireAsyncModule(id) {
|
||||
// We've already loaded all the chunks. We can require the module.
|
||||
- var promise = __webpack_require__(id);
|
||||
+ var promise = __bsky_require__(id);
|
||||
|
||||
if (typeof promise.then !== 'function') {
|
||||
// This wasn't a promise after all.
|
||||
@@ -3365,7 +3365,7 @@
|
||||
// Increase priority if necessary.
|
||||
|
||||
function requireModule(metadata) {
|
||||
- var moduleExports = __webpack_require__(metadata[ID]);
|
||||
+ var moduleExports = __bsky_require__(metadata[ID]);
|
||||
|
||||
if (isAsyncImport(metadata)) {
|
||||
if (typeof moduleExports.then !== 'function') ; else if (moduleExports.status === 'fulfilled') {
|
||||
@@ -3397,9 +3397,9 @@
|
||||
* of chunks that come from Flight and may not be known to the webpack runtime
|
||||
*/
|
||||
|
||||
- var webpackGetChunkFilename = __webpack_require__.u;
|
||||
+ var webpackGetChunkFilename = __bsky_require__.u;
|
||||
|
||||
- __webpack_require__.u = function (chunkId) {
|
||||
+ __bsky_require__.u = function (chunkId) {
|
||||
var flightChunk = chunkMap.get(chunkId);
|
||||
|
||||
if (flightChunk !== undefined) {
|
||||
@@ -3411,7 +3411,7 @@
|
||||
|
||||
function loadChunk(chunkId, filename) {
|
||||
chunkMap.set(chunkId, filename);
|
||||
- return __webpack_chunk_load__(chunkId);
|
||||
+ return __bsky_chunk_load__(chunkId);
|
||||
}
|
||||
|
||||
// The server acts as a Client of itself when resolving Server References.
|
||||
diff --git a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js
|
||||
index 88d650b..8ab5d37 100644
|
||||
--- a/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js
|
||||
+++ b/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js
|
||||
@@ -39,8 +39,8 @@ function J(a,b,c,e){c={digest:c};b=b.toString(16)+":E"+K(c)+"\n";b=C.encode(b);a
|
||||
h);b.thenableState=za()}else{a.abortableTasks.delete(b);b.status=4;var k=w(a,f);J(a,b.id,k)}}finally{}}function Ia(a){var b=I.H;I.H=vb;var c=l;S=l=a;try{var e=a.pingedTasks;a.pingedTasks=[];for(var d=0;d<e.length;d++)La(a,e[d]);null!==a.destination&&Q(a,a.destination)}catch(g){w(a,g),pa(a,g)}finally{I.H=b,S=null,l=c}}function Q(a,b){r=new Uint8Array(2048);q=0;try{for(var c=a.completedImportChunks,e=0;e<c.length;e++)a.pendingChunks--,V(b,c[e]);c.splice(0,e);var d=a.completedHintChunks;for(e=0;e<d.length;e++)V(b,
|
||||
d[e]);d.splice(0,e);var g=a.completedRegularChunks;for(e=0;e<g.length;e++)a.pendingChunks--,V(b,g[e]);g.splice(0,e);var f=a.completedErrorChunks;for(e=0;e<f.length;e++)a.pendingChunks--,V(b,f[e]);f.splice(0,e)}finally{a.flushScheduled=!1,r&&0<q&&(b.enqueue(new Uint8Array(r.buffer,0,q)),r=null,q=0)}0===a.pendingChunks&&b.close()}function qa(a,b){try{var c=a.abortableTasks;if(0<c.size){a.pendingChunks++;var e=a.nextChunkId++,d=void 0===b?Error("The render was aborted by the server without a reason."):
|
||||
b,g=w(a,d);J(a,e,g,d);c.forEach(function(f){f.status=3;var h=t(e);f=Ja(a,f.id,h);a.completedErrorChunks.push(f)});c.clear()}null!==a.destination&&Q(a,a.destination)}catch(f){w(a,f),pa(a,f)}}function Oa(a,b){var c="",e=a[b];if(e)c=e.name;else{var d=b.lastIndexOf("#");-1!==d&&(c=b.slice(d+1),e=a[b.slice(0,d)]);if(!e)throw Error('Could not find the module "'+b+'" in the React Server Manifest. This is probably a bug in the React Server Components bundler.');}return[e.id,e.chunks,c]}function Pa(a){var b=
|
||||
-__webpack_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function wb(){}function Qa(a){for(var b=a[1],c=[],e=0;e<b.length;){var d=b[e++],g=b[e++],f=da.get(d);void 0===f?(Ra.set(d,g),g=__webpack_chunk_load__(d),c.push(g),f=da.set.bind(da,d,null),g.then(f,wb),da.set(d,g)):null!==f&&c.push(f)}return 4===a.length?0===c.length?Pa(a[0]):Promise.all(c).then(function(){return Pa(a[0])}):
|
||||
-0<c.length?Promise.all(c):null}function M(a){var b=__webpack_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}function ea(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}function Sa(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}function Ta(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&Sa(c,b)}}
|
||||
+__bsky_require__(a);if("function"!==typeof b.then||"fulfilled"===b.status)return null;b.then(function(c){b.status="fulfilled";b.value=c},function(c){b.status="rejected";b.reason=c});return b}function wb(){}function Qa(a){for(var b=a[1],c=[],e=0;e<b.length;){var d=b[e++],g=b[e++],f=da.get(d);void 0===f?(Ra.set(d,g),g=__bsky_chunk_load__(d),c.push(g),f=da.set.bind(da,d,null),g.then(f,wb),da.set(d,g)):null!==f&&c.push(f)}return 4===a.length?0===c.length?Pa(a[0]):Promise.all(c).then(function(){return Pa(a[0])}):
|
||||
+0<c.length?Promise.all(c):null}function M(a){var b=__bsky_require__(a[0]);if(4===a.length&&"function"===typeof b.then)if("fulfilled"===b.status)b=b.value;else throw b.reason;return"*"===a[2]?b:""===a[2]?b.__esModule?b.default:b:b[a[2]]}function ea(a,b,c,e){this.status=a;this.value=b;this.reason=c;this._response=e}function Sa(a,b){for(var c=0;c<a.length;c++)(0,a[c])(b)}function Ta(a,b){if("pending"===a.status||"blocked"===a.status){var c=a.reason;a.status="rejected";a.reason=b;null!==c&&Sa(c,b)}}
|
||||
function xb(a,b,c,e,d,g){var f=Oa(a._bundlerConfig,b);a=Qa(f);if(c)c=Promise.all([c,a]).then(function(h){h=h[0];var k=M(f);return k.bind.apply(k,[null].concat(h))});else if(a)c=Promise.resolve(a).then(function(){return M(f)});else return M(f);c.then(Ua(e,d,g),Va(e));return null}function ra(a){var b=T,c=x;T=a;x=null;try{var e=JSON.parse(a.value,a._response._fromJSON);null!==x&&0<x.deps?(x.value=e,a.status="blocked",a.value=null,a.reason=null):(a.status="fulfilled",a.value=e)}catch(d){a.status="rejected",
|
||||
a.reason=d}finally{T=b,x=c}}function yb(a,b){a._chunks.forEach(function(c){"pending"===c.status&&Ta(c,b)})}function U(a,b){var c=a._chunks,e=c.get(b);e||(e=a._formData.get(a._prefix+b),e=null!=e?new ea("resolved_model",e,null,a):new ea("pending",null,null,a),c.set(b,e));return e}function Ua(a,b,c){if(x){var e=x;e.deps++}else e=x={deps:1,value:null};return function(d){b[c]=d;e.deps--;0===e.deps&&"blocked"===a.status&&(d=a.value,a.status="fulfilled",a.value=e.value,null!==d&&Sa(d,e.value))}}function Va(a){return function(b){return Ta(a,
|
||||
b)}}function sa(a,b){a=U(a,b);"resolved_model"===a.status&&ra(a);if("fulfilled"!==a.status)throw a.reason;return a.value}function zb(a,b,c,e){if("$"===e[0])switch(e[1]){case "$":return e.slice(1);case "@":return b=parseInt(e.slice(2),16),U(a,b);case "F":return e=parseInt(e.slice(2),16),e=sa(a,e),xb(a,e.id,e.bound,T,b,c);case "T":return gb(e.slice(2));case "Q":return b=parseInt(e.slice(2),16),a=sa(a,b),new Map(a);case "W":return b=parseInt(e.slice(2),16),a=sa(a,b),new Set(a);case "K":b=e.slice(2);
|
||||
@@ -58,7 +58,7 @@ Bb=Symbol.for("react.memo_cache_sentinel");Symbol.for("react.postpone");var Ma=S
|
||||
X=null,S=null,na=0,G=null,vb={useMemo:function(a){return a()},useCallback:function(a){return a},useDebugValue:function(){},useDeferredValue:u,useTransition:u,readContext:ia,useContext:ia,useReducer:u,useRef:u,useState:u,useInsertionEffect:u,useLayoutEffect:u,useImperativeHandle:u,useEffect:u,useId:function(){if(null===S)throw Error("useId can only be used while React is rendering");var a=S.identifierCount++;return":"+S.identifierPrefix+"S"+a.toString(32)+":"},useSyncExternalStore:u,useCacheRefresh:function(){return jb},
|
||||
useMemoCache:function(a){for(var b=Array(a),c=0;c<a;c++)b[c]=Bb;return b},use:function(a){if(null!==a&&"object"===typeof a||"function"===typeof a){if("function"===typeof a.then){var b=na;na+=1;null===G&&(G=[]);return ib(G,a,b)}a.$$typeof===ab&&ia()}if(a.$$typeof===F){if(null!=a.value&&a.value.$$typeof===ab)throw Error("Cannot read a Client Context from a Server Component.");throw Error("Cannot use() an already resolved Client Reference.");}throw Error("An unsupported type was passed to use(): "+String(a));
|
||||
}},Da={getCacheForType:function(a){var b=(b=l?l:null)?b.cache:new Map;var c=b.get(a);void 0===c&&(c=a(),b.set(a,c));return c}},ka=Array.isArray,Na=Object.getPrototypeOf,la=Symbol.for("react.client.reference"),bb=cb.__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;if(!bb)throw Error('The "react" package in this environment is not configured correctly. The "react-server" condition must be enabled in any environment that runs React Server Components.');var I=bb,ub=Object.prototype,K=JSON.stringify,
|
||||
-l=null,B=!1,aa={},da=new Map,Ra=new Map,Cb=__webpack_require__.u;__webpack_require__.u=function(a){var b=Ra.get(a);return void 0!==b?b:Cb(a)};ea.prototype=Object.create(Promise.prototype);ea.prototype.then=function(a,b){switch(this.status){case "resolved_model":ra(this)}switch(this.status){case "fulfilled":a(this.value);break;case "pending":case "blocked":a&&(null===this.value&&(this.value=[]),this.value.push(a));b&&(null===this.reason&&(this.reason=[]),this.reason.push(b));break;default:b(this.reason)}};
|
||||
+l=null,B=!1,aa={},da=new Map,Ra=new Map,Cb=__bsky_require__.u;__bsky_require__.u=function(a){var b=Ra.get(a);return void 0!==b?b:Cb(a)};ea.prototype=Object.create(Promise.prototype);ea.prototype.then=function(a,b){switch(this.status){case "resolved_model":ra(this)}switch(this.status){case "fulfilled":a(this.value);break;case "pending":case "blocked":a&&(null===this.value&&(this.value=[]),this.value.push(a));b&&(null===this.reason&&(this.reason=[]),this.reason.push(b));break;default:b(this.reason)}};
|
||||
var T=null,x=null;p.createClientModuleProxy=function(a){a=E({},a,!1);return new Proxy(a,wa)};p.decodeAction=function(a,b){var c=new FormData,e=null;a.forEach(function(d,g){g.startsWith("$ACTION_")?g.startsWith("$ACTION_REF_")?(d="$ACTION_"+g.slice(12)+":",d=Za(a,b,d),e=Ya(b,d.id,d.bound)):g.startsWith("$ACTION_ID_")&&(d=g.slice(11),e=Ya(b,d,null)):c.append(g,d)});return null===e?null:e.then(function(d){return d.bind(null,c)})};p.decodeFormState=function(a,b,c){var e=b.get("$ACTION_KEY");if("string"!==
|
||||
typeof e)return Promise.resolve(null);var d=null;b.forEach(function(f,h){h.startsWith("$ACTION_REF_")&&(f="$ACTION_"+h.slice(12)+":",d=Za(b,c,f))});if(null===d)return Promise.resolve(null);var g=d.id;return Promise.resolve(d.bound).then(function(f){return null===f?null:[a,e,g,f.length-1]})};p.decodeReply=function(a,b){if("string"===typeof a){var c=new FormData;c.append("0",a);a=c}a=Wa(b,"",a);b=U(a,0);Xa(a);return b};p.registerClientReference=function(a,b,c){return E(a,b+"#"+c,!1)};p.registerServerReference=
|
||||
function(a,b,c){return Object.defineProperties(a,{$$typeof:{value:W},$$id:{value:null===c?b:b+"#"+c,configurable:!0},$$bound:{value:null,configurable:!0},bind:{value:ua,configurable:!0}})};p.renderToReadableStream=function(a,b,c){var e=ob(a,b,c?c.onError:void 0,c?c.identifierPrefix:void 0,c?c.onPostpone:void 0);if(c&&c.signal){var d=c.signal;if(d.aborted)qa(e,d.reason);else{var g=function(){qa(e,d.reason);d.removeEventListener("abort",g)};d.addEventListener("abort",g)}}return new ReadableStream({type:"bytes",
|
||||
@@ -0,0 +1,16 @@
|
||||
const {withEntitlementsPlist} = require('@expo/config-plugins')
|
||||
|
||||
const withAppEntitlements = config => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withEntitlementsPlist(config, async config => {
|
||||
config.modResults['com.apple.security.application-groups'] = [
|
||||
`group.app.bsky`,
|
||||
]
|
||||
config.modResults[
|
||||
'com.apple.developer.associated-appclip-app-identifiers'
|
||||
] = [`$(AppIdentifierPrefix)${config.ios.bundleIdentifier}.AppClip`]
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {withAppEntitlements}
|
||||
@@ -0,0 +1,32 @@
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withClipEntitlements = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const entitlementsPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
targetName,
|
||||
`${targetName}.entitlements`,
|
||||
)
|
||||
|
||||
const appClipEntitlements = {
|
||||
'com.apple.security.application-groups': [`group.app.bsky`],
|
||||
'com.apple.developer.parent-application-identifiers': [
|
||||
`$(AppIdentifierPrefix)${config.ios.bundleIdentifier}`,
|
||||
],
|
||||
'com.apple.developer.associated-domains': config.ios.associatedDomains,
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(entitlementsPath), {
|
||||
recursive: true,
|
||||
})
|
||||
fs.writeFileSync(entitlementsPath, plist.default.build(appClipEntitlements))
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {withClipEntitlements}
|
||||
@@ -0,0 +1,46 @@
|
||||
const {withInfoPlist} = require('@expo/config-plugins')
|
||||
const plist = require('@expo/plist')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const withClipInfoPlist = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withInfoPlist(config, config => {
|
||||
const targetPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
targetName,
|
||||
'Info.plist',
|
||||
)
|
||||
|
||||
const newPlist = plist.default.build({
|
||||
NSAppClip: {
|
||||
NSAppClipRequestEphemeralUserNotification: false,
|
||||
NSAppClipRequestLocationConfirmation: false,
|
||||
},
|
||||
UILaunchScreen: {},
|
||||
CFBundleName: '$(PRODUCT_NAME)',
|
||||
CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)',
|
||||
CFBundleVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
CFBundleExecutable: '$(EXECUTABLE_NAME)',
|
||||
CFBundlePackageType: '$(PRODUCT_BUNDLE_PACKAGE_TYPE)',
|
||||
CFBundleShortVersionString: config.version,
|
||||
CFBundleIconName: 'AppIcon',
|
||||
UIViewControllerBasedStatusBarAppearance: 'NO',
|
||||
UISupportedInterfaceOrientations: [
|
||||
'UIInterfaceOrientationPortrait',
|
||||
'UIInterfaceOrientationPortraitUpsideDown',
|
||||
],
|
||||
'UISupportedInterfaceOrientations~ipad': [
|
||||
'UIInterfaceOrientationPortrait',
|
||||
'UIInterfaceOrientationPortraitUpsideDown',
|
||||
],
|
||||
})
|
||||
|
||||
fs.mkdirSync(path.dirname(targetPath), {recursive: true})
|
||||
fs.writeFileSync(targetPath, newPlist)
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {withClipInfoPlist}
|
||||
@@ -0,0 +1,40 @@
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const FILES = ['AppDelegate.swift', 'ViewController.swift']
|
||||
|
||||
const withFiles = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const basePath = path.join(
|
||||
config.modRequest.projectRoot,
|
||||
'modules',
|
||||
targetName,
|
||||
)
|
||||
|
||||
for (const file of FILES) {
|
||||
const sourcePath = path.join(basePath, file)
|
||||
const targetPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
targetName,
|
||||
file,
|
||||
)
|
||||
|
||||
fs.mkdirSync(path.dirname(targetPath), {recursive: true})
|
||||
fs.copyFileSync(sourcePath, targetPath)
|
||||
}
|
||||
|
||||
const imagesBasePath = path.join(basePath, 'Images.xcassets')
|
||||
const imagesTargetPath = path.join(
|
||||
config.modRequest.platformProjectRoot,
|
||||
targetName,
|
||||
'Images.xcassets',
|
||||
)
|
||||
fs.cpSync(imagesBasePath, imagesTargetPath, {recursive: true})
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {withFiles}
|
||||
@@ -0,0 +1,40 @@
|
||||
const {withPlugins} = require('@expo/config-plugins')
|
||||
const {withAppEntitlements} = require('./withAppEntitlements')
|
||||
const {withClipEntitlements} = require('./withClipEntitlements')
|
||||
const {withClipInfoPlist} = require('./withClipInfoPlist')
|
||||
const {withFiles} = require('./withFiles')
|
||||
const {withXcodeTarget} = require('./withXcodeTarget')
|
||||
|
||||
const APP_CLIP_TARGET_NAME = 'BlueskyClip'
|
||||
|
||||
const withStarterPackAppClip = config => {
|
||||
return withPlugins(config, [
|
||||
withAppEntitlements,
|
||||
[
|
||||
withClipEntitlements,
|
||||
{
|
||||
targetName: APP_CLIP_TARGET_NAME,
|
||||
},
|
||||
],
|
||||
[
|
||||
withClipInfoPlist,
|
||||
{
|
||||
targetName: APP_CLIP_TARGET_NAME,
|
||||
},
|
||||
],
|
||||
[
|
||||
withFiles,
|
||||
{
|
||||
targetName: APP_CLIP_TARGET_NAME,
|
||||
},
|
||||
],
|
||||
[
|
||||
withXcodeTarget,
|
||||
{
|
||||
targetName: APP_CLIP_TARGET_NAME,
|
||||
},
|
||||
],
|
||||
])
|
||||
}
|
||||
|
||||
module.exports = withStarterPackAppClip
|
||||
@@ -0,0 +1,91 @@
|
||||
const {withXcodeProject} = require('@expo/config-plugins')
|
||||
|
||||
const BUILD_PHASE_FILES = ['AppDelegate.swift', 'ViewController.swift']
|
||||
|
||||
const withXcodeTarget = (config, {targetName}) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return withXcodeProject(config, config => {
|
||||
const pbxProject = config.modResults
|
||||
|
||||
const target = pbxProject.addTarget(targetName, 'application', targetName)
|
||||
target.pbxNativeTarget.productType = `"com.apple.product-type.application.on-demand-install-capable"`
|
||||
pbxProject.addBuildPhase(
|
||||
BUILD_PHASE_FILES.map(f => `${targetName}/${f}`),
|
||||
'PBXSourcesBuildPhase',
|
||||
'Sources',
|
||||
target.uuid,
|
||||
'application',
|
||||
'"AppClips"',
|
||||
)
|
||||
pbxProject.addBuildPhase(
|
||||
[`${targetName}/Images.xcassets`],
|
||||
'PBXResourcesBuildPhase',
|
||||
'Resources',
|
||||
target.uuid,
|
||||
'application',
|
||||
'"AppClips"',
|
||||
)
|
||||
|
||||
const pbxGroup = pbxProject.addPbxGroup([
|
||||
'AppDelegate.swift',
|
||||
'ViewController.swift',
|
||||
'Images.xcassets',
|
||||
`${targetName}.entitlements`,
|
||||
'Info.plist',
|
||||
])
|
||||
|
||||
pbxProject.addFile(`${targetName}/Info.plist`, pbxGroup.uuid)
|
||||
const configurations = pbxProject.pbxXCBuildConfigurationSection()
|
||||
for (const key in configurations) {
|
||||
if (typeof configurations[key].buildSettings !== 'undefined') {
|
||||
const buildSettingsObj = configurations[key].buildSettings
|
||||
if (
|
||||
typeof buildSettingsObj.PRODUCT_NAME !== 'undefined' &&
|
||||
buildSettingsObj.PRODUCT_NAME === `"${targetName}"`
|
||||
) {
|
||||
buildSettingsObj.CLANG_ENABLE_MODULES = 'YES'
|
||||
buildSettingsObj.INFOPLIST_FILE = `"${targetName}/Info.plist"`
|
||||
buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `"${targetName}/${targetName}.entitlements"`
|
||||
buildSettingsObj.CODE_SIGN_STYLE = 'Automatic'
|
||||
buildSettingsObj.CURRENT_PROJECT_VERSION = `"${
|
||||
process.env.BSKY_IOS_BUILD_NUMBER ?? '1'
|
||||
}"`
|
||||
buildSettingsObj.GENERATE_INFOPLIST_FILE = 'YES'
|
||||
buildSettingsObj.MARKETING_VERSION = `"${config.version}"`
|
||||
buildSettingsObj.PRODUCT_BUNDLE_IDENTIFIER = `"${config.ios?.bundleIdentifier}.AppClip"`
|
||||
buildSettingsObj.SWIFT_EMIT_LOC_STRINGS = 'YES'
|
||||
buildSettingsObj.SWIFT_VERSION = '5.0'
|
||||
buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1"`
|
||||
buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS'
|
||||
buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = '14.0'
|
||||
buildSettingsObj.ASSETCATALOG_COMPILER_APPICON_NAME = 'AppIcon'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS', targetName)
|
||||
|
||||
if (!pbxProject.hash.project.objects.PBXTargetDependency) {
|
||||
pbxProject.hash.project.objects.PBXTargetDependency = {}
|
||||
}
|
||||
if (!pbxProject.hash.project.objects.PBXContainerItemProxy) {
|
||||
pbxProject.hash.project.objects.PBXContainerItemProxy = {}
|
||||
}
|
||||
pbxProject.addTargetDependency(pbxProject.getFirstTarget().uuid, [
|
||||
target.uuid,
|
||||
])
|
||||
|
||||
pbxProject.addBuildPhase(
|
||||
[`${targetName}.app`],
|
||||
'PBXCopyFilesBuildPhase',
|
||||
'Embed App Clips',
|
||||
pbxProject.getFirstTarget().uuid,
|
||||
'application',
|
||||
'"AppClips"',
|
||||
)
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {withXcodeTarget}
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
IOS_SHARE_EXTENSION_DIRECTORY="./ios/Share-with-Bluesky"
|
||||
IOS_NOTIFICATION_EXTENSION_DIRECTORY="./ios/BlueskyNSE"
|
||||
IOS_APP_CLIP_DIRECTORY="./ios/BlueskyClip"
|
||||
MODULES_DIRECTORY="./modules"
|
||||
|
||||
if [ ! -d $IOS_SHARE_EXTENSION_DIRECTORY ]; then
|
||||
@@ -16,3 +17,11 @@ if [ ! -d $IOS_NOTIFICATION_EXTENSION_DIRECTORY ]; then
|
||||
else
|
||||
cp -R $IOS_NOTIFICATION_EXTENSION_DIRECTORY $MODULES_DIRECTORY
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -d $IOS_APP_CLIP_DIRECTORY ]; then
|
||||
echo "$IOS_APP_CLIP_DIRECTORY not found inside of your iOS project."
|
||||
exit 1
|
||||
else
|
||||
cp -R $IOS_APP_CLIP_DIRECTORY $MODULES_DIRECTORY
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# production
|
||||
/build
|
||||
.eslintcache
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||