Compare commits

...

331 Commits

Author SHA1 Message Date
David Buchanan 1ad06548b1 move yt embed script to its own source file (#9337) 2025-11-06 10:18:04 -08:00
Samuel Newman f7274cbc2a always send #interactionReply (#9330) 2025-11-05 14:27:11 +02:00
Samuel Newman a232372ce7 Prevent sparse arrays in snapToOffsets (#9329)
* prevent sparse arrays in snapToOffsets

* wait, sparse arrays don't work like that

* more readable check even if it's technically useless

* make the second check actually work
2025-11-05 14:27:02 +02:00
Samuel Newman 0d1060419f add even more optional chaining to link thing (#9327) 2025-11-05 14:26:53 +02:00
Samuel Newman b41184d8e4 disable wake-from-background OTA on Android (#9317) 2025-11-05 14:26:23 +02:00
Samuel Newman caba72c848 Update web dim <html> background color (#9307)
* update dim background color

* Update other css colors

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-11-05 14:24:09 +02:00
Samuel Newman 82f7d3f4c8 remove await on dismissBrowser (#9304) 2025-10-30 17:16:20 +02:00
Samuel Newman fa98a66755 Override Reduce Motion on load-bearing animation (#9294) 2025-10-28 21:19:55 +02:00
Samuel Newman f1803068ae Android - add fallbacks when saving image to gallery (#9293)
* add fallback paths when saving image to gallery

* `debug` -> `info`
2025-10-28 21:19:36 +02:00
Samuel Newman f9a502f393 Close IAB when it triggers an intent (#8454)
* Close IAB if it opens a link to the app

* native only

* await close
2025-10-28 21:19:32 +02:00
Samuel Newman 2a7223822f Remove feed:seen from statsig (#9261)
(cherry picked from commit 78c8a6037f)
2025-10-27 13:24:12 -05:00
bnewbold 74db306096 clarify in content hider if label is on overall account (#9264)
* clarify in content hider if label is on overall account

* fix bool typo

* Update src/components/moderation/ContentHider.tsx

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* rename variable for clarity

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
(cherry picked from commit 28e132ae03)
2025-10-27 13:24:09 -05:00
Samuel Newman ca7e009836 Revert "Disable Sentry sampling for now (#9262)" (#9282)
This reverts commit de68915bc0.

(cherry picked from commit 25a2a2c7f6)
2025-10-27 13:24:04 -05:00
Eric Bailey 2a4b825637 Release 1.109.0 i18n take III (#9269) 2025-10-23 11:27:49 -05:00
Eric Bailey c00b3d1050 Don't log errors for known app password issues in chat (#9268) 2025-10-23 11:18:53 -05:00
Eric Bailey 74cb81b65f Add config to disable update overlay, disable it (#9267) 2025-10-23 11:16:55 -05:00
Samuel Newman e28aa7d1ab Patch out PerformanceMonitor component (#9266)
* patch out perf monitor component

* make PerformanceMonitor a noop

* rm debug stuff
2025-10-23 10:35:37 -05:00
Eric Bailey d4d5d6d8b9 intl:pull and intl:extract:all (#9263) 2025-10-22 15:51:19 -05:00
Eric Bailey de68915bc0 Disable Sentry sampling for now (#9262) 2025-10-22 14:08:25 -05:00
Samuel Newman ea00663e7f Android CI - Clear cache on second build (#9223)
* clear cache on second build

* alternative cache cleaning method
2025-10-22 10:05:00 -07:00
Samuel Newman 12e57378e8 fix android testflight ci (#9237) 2025-10-22 10:04:36 -07:00
Eric Bailey 24606dbcfc Don't need to be logging upstream errors (#9260) 2025-10-22 11:39:14 -05:00
Eric Bailey 6f31121e51 Check for network error before reporting push token issue (#9258) 2025-10-22 11:36:40 -05:00
Eric Bailey 1abc96314b Don't log network errors (#9257) 2025-10-22 11:36:32 -05:00
Eric Bailey 7cc897d096 Remove error log for Bluesky feed service issues (#9256) 2025-10-22 11:36:24 -05:00
Eric Bailey 6ed30213c8 Add 'Upstream service unreachable' to network errors (#9255) 2025-10-22 11:36:18 -05:00
Eric Bailey d7e8429753 Get iOS debug files into Sentry (#9242)
* Add buildArtifactPaths to iOS builds

* Update privacyManifests

* Revert changes to build configs

* Add --include-sources flag

* Add nativeCrash tester

* Sam's crash testbed

(cherry picked from commit b0a0cb7799cd900777570cae7dc10461cf811e0f)

* Output artifacts, tarball, extract and upload dSYM to Sentry

* Revert "Sam's crash testbed"

This reverts commit cc2c9828a0.

* Remove sources for other targets, include sources for main bundle

* Apply suggestion from @mozzius

* Apply suggestion from @mozzius

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-10-22 09:17:52 -05:00
Alex Benzer 6086bc5d5c Add a client event for "See more" link on suggested user interstitials (#9249) 2025-10-22 03:53:42 -07:00
Samuel Newman 199821d670 make gifs square again (#9236) 2025-10-20 07:15:01 -07:00
Eric Bailey 11dff765db Hook up post shadow to saved posts (#9225) 2025-10-20 03:05:49 -07:00
hailey 73639aa571 fix regex (#9234) 2025-10-19 09:20:53 -07:00
pfrazee f3b6d85913 Nightly source-language update 2025-10-17 02:36:59 +00:00
Samuel Newman 239d3cc535 [Release prep] 1.109.0 translations (#9218)
* intl:pull

* intl:extract:all
2025-10-16 10:08:15 -07:00
Samuel Newman d49d43f6eb update app icon (#9219) 2025-10-16 10:08:06 -07:00
Samuel Newman a482dae936 [Release prep] 1.109.0 E2E fixes (#9216)
* list tests

* fix create account

* get onboarding e2e tests working

* get curate list test working

* Update thread-muting.yml
2025-10-16 07:31:11 -07:00
Samuel Newman 25d44d8939 update xcode (#9215) 2025-10-16 07:26:52 -07:00
pfrazee e7801a0b76 Nightly source-language update 2025-10-16 02:37:46 +00:00
Eric Bailey 7861cd153d Remove notice in create account flow (#9209)
* Remove notice of policy updates

* Rm debug code
2025-10-15 13:56:29 -05:00
Samuel Newman be53a168ba Move interests clientside and add finance (#9176)
* move interests clientside

* add finance

* try and ensure there's always data for the "all" category

* add empty state for suggested accounts

* simplify error wording
2025-10-15 09:37:24 -07:00
Samuel Newman b56d0c419b add sort import suggestions plugin (#8335) 2025-10-15 09:37:07 -07:00
Samuel Newman 03adb4260f patch fractional font size issue (#9199) 2025-10-15 09:14:42 -07:00
Eric Bailey 27926f093c Fix computation of isLastSibling, isLastChild, and replyIndex (#9202)
* Fix computation of isLastSibling and isLastChild to account for muted or
otherwise hidden replies

* Update comments

* isLastSiblingByCounts isn't needed, should rely only on the count of replies seen

* The counters serve the same purpose, we only need to know the count of the actual replies rendered to the view in order to calculate the replyIndex

* Remove redundant check
2025-10-15 11:13:08 -05:00
Samuel Newman fe5a623b44 Haptic on profile buttons (#6034)
* play haptic on profile buttons

* change react import
2025-10-15 09:10:26 -07:00
Samuel Newman fc944841ce add large content viewer to tabs (#8394) 2025-10-15 11:02:47 -05:00
Samuel Newman 66ca3e34a4 fix invalid tweaking to date string (#8415) 2025-10-15 11:00:29 -05:00
Samuel Newman 4788106f32 change flatlist config for explore page (#9083) 2025-10-15 08:57:30 -07:00
Samuel Newman 6414bda252 delay toast after posting (#9208) 2025-10-15 08:56:35 -07:00
Samuel Newman 591fd262b3 make sure border radius matches inset border (#9207) 2025-10-15 08:55:46 -07:00
Samuel Newman ede10368d5 cache pages in explore feed previews (#9082) 2025-10-15 18:54:54 +03:00
Samuel Newman 1127e50248 [Onboarding] Add starterpacks step (#9007)
* add onboarding starterpacks step

* tweak starter pack cards

* fix web styling

* use new API

* add screen transitions to onboarding

* skip host account

* bump limit to 6

* tweak font weight

* rm commented out code

* fix yarn.lock

* limit starter packs experiment to english speakers
2025-10-15 07:32:38 -07:00
jim e4485da4ff Merge pull request #9189 from internet-development/binaryfiddler/hashtag
prompt user to log in on hashtag screen
2025-10-15 01:12:49 -07:00
Alex Benzer 6c7fd96528 Add metrics for when OPs click "hide reply for me" or "hide reply for everyone" (#9206) 2025-10-15 08:33:30 +01:00
pfrazee 6a1c0ff346 Nightly source-language update 2025-10-15 02:38:20 +00:00
Chenyu Huang ad18439148 use hasSession 2025-10-14 14:15:19 -07:00
Eric Bailey 7280e44ea2 Remove log (#9204) 2025-10-14 11:49:02 -05:00
Eric Bailey 7e6b23bd86 Fix ternary in moderation settings that caused a double title (#9198) 2025-10-14 09:17:34 -05:00
pfrazee d2bc22e9be Nightly source-language update 2025-10-14 02:36:22 +00:00
Eric Bailey 0b50e9f109 Remove remaining usages of old post thread query (#9184)
* Remove remaining usages of old post thread query

* Add PostThreadContext, cache mutator for threadgates on threads, pipe it through

* Replace getPostThread in threadgate query

* Replace in initQuote handling, which isn't even used rn...

* Missing import

* Revert ext change
2025-10-13 18:36:11 -05:00
Samuel Newman 0f6a99c8a5 enable numberOfLines on replyTo component (#9196) 2025-10-13 09:36:06 -07:00
Samuel Newman ea9986f127 fix label spacing (#9195) 2025-10-13 09:35:31 -07:00
Samuel Newman 5222a6e1c3 tidy up post reasons (#9194) 2025-10-13 08:24:02 -07:00
pfrazee b7f505ab00 Nightly source-language update 2025-10-11 02:32:55 +00:00
Chenyu Huang 34f2cc2322 prompt user to log in on hashtag screen 2025-10-10 14:17:57 -07:00
Eric Bailey 8a30131686 Combine SubtleWebHover and SubtleHover, default to web only for all cases (#9188) 2025-10-10 15:05:20 -05:00
Samuel Newman 69421d1f33 Change anchor text size to lg from xl (#9185) 2025-10-10 14:32:56 -05:00
Eric Bailey 995808e816 Bump alf package to include new colors (#9187) 2025-10-10 14:30:15 -05:00
Samuel Newman 508fa2aced Value prop screen - polish, convert to pager (#9133)
* turn value prop screen into a pager on native

* rm ts-ingore

* fix pager swipe on android

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-10-10 14:05:22 -05:00
Samuel Newman d528ae791f Fix add/edit moderation list form placeholder text (#9074)
* fix placeholder text of modlist dialog

* change header font weight

* fix string
2025-10-10 13:57:42 -05:00
Samuel Newman b15903e0cb move feed context on desktop (#9015) 2025-10-10 13:56:57 -05:00
Samuel Newman 9bea450803 Fix profile label section soft reset scroll (#9103)
* Fix profile label section scrollto

* revert back to headerHeight
2025-10-10 13:56:23 -05:00
Samuel Newman fb5d56422a fix border color of feed/list embeds (#9021) 2025-10-10 13:55:43 -05:00
Samuel Newman ad38d905e4 fix newline backspace behaviour (#9038) 2025-10-10 13:55:16 -05:00
Samuel Newman 2b34721876 fix android admonition flex collapse issue (#9120) 2025-10-10 11:43:23 -05:00
Samuel Newman c928d6b14a Keep the screen awake on the video feed (#9146) 2025-10-10 11:33:09 -05:00
Samuel Newman 0dd71fb087 Add accessibilityRole to single images (#9148) 2025-10-10 11:31:12 -05:00
Samuel Newman 54b8eacba1 Fix chat request buttons not moving with swipe gesture (#9155)
* portal in buttons so they move with swipe

* remove outline style buttons
2025-10-10 11:30:29 -05:00
Samuel Newman b7b47d30bf fix hider alignment in thread (#9168) 2025-10-10 11:26:48 -05:00
Samuel Newman 3561fc8112 move aspect ratio to atom (#9171) 2025-10-10 11:20:41 -05:00
Samuel Newman fe31cf4508 fix background colors (#9174) 2025-10-10 11:17:57 -05:00
Eric Bailey a9efd94983 Placeholder style tweaks (#9107)
* Align colors in loading placeholders

* Same for new skele components

* Increase opacity of disabled replies button slightly
2025-10-10 11:16:40 -05:00
Samuel Newman 6cd6a3447d Tweak greens (#9177)
* tweak greens

* undo test code
2025-10-10 09:15:48 -07:00
Samuel Newman ec8c36e614 add 10% sample rate to sentry (#9182) 2025-10-10 08:31:38 -07:00
Samuel Newman 84cae44cd2 Ship activation experiments (#9170)
* ship onboarding experiments

* delete logged out cta

* delete other loggedoutcta
2025-10-10 08:24:42 -07:00
Eric Bailey 5bf1141b55 Add validation to threadgate records, check for max hidden replies (#9178) 2025-10-10 10:03:47 -05:00
pfrazee e1ee85622d Nightly source-language update 2025-10-10 02:36:26 +00:00
easrng b1dc5179b6 reply button in feeds opens thread (#9143)
* the reply button should open the replies to a post, not present a text input for you to enter your Big Thought into. i think a lot of people don’t even realize that the point has already been written

* Feature gate, fix event

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-10-09 20:29:50 -05:00
Samuel Newman 96d77841e0 Fix language prompt text wrap issue (#9175) 2025-10-09 17:18:24 +03:00
Alex Benzer b38edc52b8 Auto-select search results tab (#9159)
When the user clicks the search button next to "Discover New Feeds" or "Suggested Accounts" on the Explore page, we'll auto-select the respective search results tab (feeds or users).
2025-10-09 05:02:38 -07:00
jim d68fc78e93 Merge pull request #9167 from internet-development/elijaharita/structured-data-name-fallback
fix: show both DisplayName and Handle in google structured data (or only Handle if DisplayName not set)
2025-10-08 14:38:07 -07:00
Elijah Seed-Arita 028ed2f8e2 fix: copy-paste mistake and more idiomatic structured data format 2025-10-08 10:45:30 -07:00
Elijah Seed-Arita 67049b98c9 fix: show both DisplayName and Handle in google structured data (or only Handle if DisplayName not set) 2025-10-08 10:29:44 -07:00
jim 07bcf7460a Merge pull request #9153 from bluesky-social/hailey/self-labels-check-embed
check for self labels when determining if we should hide embeds
2025-10-07 17:14:49 -07:00
Samuel Newman 54f70a576a [16KB] use 16kb-compatible fork of react-native-mmkv (#9150)
* use 16kb-compatible fork of react-native-mmkv

* avoid using package alias

* specify ndk version

* Revert "specify ndk version"

This reverts commit 577393518e.

* add compiler flags to mmkv build (bump version)

* move fork to bsky repo/npm org
2025-10-07 08:06:29 -07:00
Hailey 4a9185b0f5 nit 2025-10-06 20:41:04 -07:00
Hailey 1354bf987d check for self labels when determining if we should hide embeds 2025-10-06 20:39:43 -07:00
Elijah Seed-Arita 7f4e302b85 feat: hide embeds for moderated posts (#9152) 2025-10-06 19:34:26 -07:00
Samuel Newman 5e36992fba patch delete audio compression for 16kb compat (#9149) 2025-10-06 07:42:53 -07:00
Eduardo Rosendo 42a427cfd6 feat(link-meta): Tweaks getLinkMeta to whitelist trusted hostnames (#9139) 2025-10-06 16:36:33 +03:00
pfrazee 0c0c21a0aa Nightly source-language update 2025-10-04 02:30:44 +00:00
Samuel Newman f170313f70 patch sonner-native to remove exit animations (#9092) 2025-10-03 13:08:05 -07:00
Samuel Newman c8d0662690 remove gate (#9130) 2025-10-03 12:44:43 -07:00
Samuel Newman cf4cce2225 Embed testbed with tweaks (#9115)
* Nightly source-language update

* add: test embed page for bskyembeds

* run prettier

* wip

* feat: use local dev for bskyembed test.html

* feat: commit generated files which i'm not sure about

* chore: add basic readme to bskyembed

* Revert "feat: commit generated files which i'm not sure about"

This reverts commit b891efdaa1fd7ddd4417bfd5267e861a25a93b0e.

* chore: revert change to bskyembed/.eslintrc.cjs

* chore: remove generated file from bskyweb/static

* chore: revert messages.po changes

* add dev-snippet command, add dev router

* Update README.md

* Update bskyembed/test.html

* change IS_DEV check to allow localhost

---------

Co-authored-by: APiligrim <28320272+APiligrim@users.noreply.github.com>
Co-authored-by: Anastasiya Uraleva <anastasiya@Anastasiyas-MacBook-Pro.local>
Co-authored-by: Anastasiya Uraleva <anastasiya@Mac.localdomain>
Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com>
2025-10-03 12:15:26 -07:00
Samuel Newman 0e9e705d99 fix android onboarding dev crash (#9132) 2025-10-03 12:07:21 -07:00
Chenyu 12e09a9bd1 starter pack metrics (#9116)
* starter pack metrics

* emit starterpack:add/remove user event with starterpack uri in starter pack dialog

* rename metadata field
2025-10-03 10:17:59 -07:00
dan 885356256e [Fix Logouts] Persist accounts synchronously (#9109)
* Make persisting synchronous

* Initialize later so persisted is filled
2025-10-03 11:02:52 -05:00
pfrazee b68f80050c Nightly source-language update 2025-10-03 02:34:02 +00:00
dan 1354fd2661 [Fix Logouts] Remove buggy hackfix (#9108)
* Rip out the network hack in favor of bluesky-social/atproto#4238

* Bump api pkg

* Debug code

* Revert "Debug code"

This reverts commit 38445f31f4.

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-10-02 14:01:10 -05:00
Samuel Newman 77bf49f212 Tighten up eslint rule to catch unused const {_} = useLingui() (#9122)
* tighten eslint config to catch unused `useLingui`s

* fix now-invalid uses of _

* remove unused function that produces a ton of eslint warnings

* upgrade react compiler plugin
2025-10-02 09:59:05 -07:00
Eric Bailey 02b189a40e Language selection and suggestion UX improvements (#9067)
* feat: don't retain accepted language suggestion after finishing or exiting post (#8886)

* feat: don't retain accepted language suggestion after finishing or exiting post

* fix: rebase fixes

* fix: rebase fixes

* chore: lint

* Rename onChange for clarity

* Improve logic in composer

* Handle user override more explicitly

* Drill in onSelectLanguage callback into dialog too

* Fix typo

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Make text crystal clear

* Handle multiple languages

---------

Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com>
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2025-10-02 09:18:52 -05:00
Daniel Holmgren 5fd52b3d30 add patent pledge link to readme (#9118) 2025-10-01 11:49:06 -07:00
Samuel Newman 1e6a44f2e8 Fix feedfeedback metrics not distinguishing which feed it's from (#9099)
* fix feedfeedback metrics being sent for all feeds

* remove `discover:` metrics
2025-10-01 06:09:12 -07:00
pfrazee 144d61ef76 Nightly source-language update 2025-10-01 02:40:10 +00:00
Eric Bailey 381feacd26 Add StackedButton component (#9086) 2025-09-30 16:18:36 -05:00
dan 5df6036c2c [Web] Fix thread jumps (#9111)
* [Web] Fix thread jumps

* Comment formatting

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-09-30 10:46:31 -05:00
Eric Bailey 3e55e52858 Button tweaks (#9106)
* Tweak button colors

* Semi bold only for tiny buttons
2025-09-30 08:40:21 -05:00
Chenyu f82a6188e6 Update admonition component (#9068)
* update admonition component

* fix linting, adominition alighment

* design tweak

* edge cases for admonition, update storybook

* Update src/components/Admonition.tsx

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* fix mobile version

* change button style

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-09-30 05:27:19 -07:00
Samuel Newman c7ce827bfe fix gap on profile (#9081) 2025-09-30 07:28:50 +03:00
pfrazee 9e0f192546 Nightly source-language update 2025-09-30 02:34:39 +00:00
Samuel Newman d3dbb94689 Catch errors on geolocation request, reduce Sentry logs (#9098) 2025-09-29 23:12:50 +03:00
Samuel Newman 031fa95715 remove root sibling library (#9097) 2025-09-29 14:31:55 -05:00
Samuel Newman ae2c9a832f don't send some "activity no longer available" errors (#9100) 2025-09-29 14:15:38 -05:00
Samuel Newman 02a25d2aa2 Log OTA errors properly (#9101)
* Log OTA errors properly

* filter out network errors
2025-09-29 14:14:39 -05:00
Samuel Newman 2b32fff1d0 Fix link crash (#9102)
* fix link crash

* fix link crash
2025-09-29 14:14:13 -05:00
dan 22bb9c599f Send inferrable interactions to third-party feeds (#9094) 2025-09-29 14:07:21 -05:00
Samuel Newman 7f4e3091f7 patch react native screens (#9087) 2025-09-26 10:41:17 -07:00
jim 6114b33ce2 Merge pull request #8924 from internet-development/elijaharita/google-structured-data
feat: google structured data for profiles and posts
2025-09-26 09:27:39 -07:00
Elijah Seed-Arita 305ea8bdfb fix: remove escapejs and fix profile issues 2025-09-25 20:14:46 -07:00
Elijah Seed-Arita a322ae412a feat: profile.html escapejs 2025-09-25 20:14:46 -07:00
Elijah Seed-Arita caa7736035 feat: post.html structured content, wip 2025-09-25 20:14:46 -07:00
Elijah Seed-Arita 655964ec0b feat: profile.html structured content 2025-09-25 20:14:46 -07:00
Samuel Newman 66526a8e10 Fix extToMime function (#9084) 2025-09-25 10:26:25 -05:00
pfrazee 8cded435d1 Nightly source-language update 2025-09-25 02:36:36 +00:00
Eric Bailey bd17f04810 Migrate to @bsky.app/alf package (#9030)
* Add storybook shortcut in dev

* Migrate to alg pkg

* Migrate font_bold to new font_semi_bold

* Migrate font_heavy to font_bold

* Fix old theme refs

* Remove leading util

* Bump

* Revert "Add storybook shortcut in dev"

This reverts commit 7a86195c77fb5d449c7782e64ebabb6addfab919.

* Remove alf script

* Adjust font scale

* Bump

* Bump pkg

* Migrate values from conflicts

* Create default themes outside react

* Use built-in alf utils

* Migrate old font styles

* Remove unused tokens

* Move theme creation to more appropriate file

* Update button colors

* Adjust TextField colors, line heights
2025-09-24 11:47:08 -05:00
pfrazee bdc58045cf Nightly source-language update 2025-09-24 02:35:40 +00:00
Austin McKinley 0482e917b6 Merge pull request #9069 from bluesky-social/migrate-blink
Start migrating blink. Also add better shutdown handling for ogcard
2025-09-23 16:36:22 -07:00
Austin McKinley 0bc2355226 Start migrating blink. Also add better shutdown handling for ogcard 2025-09-23 15:17:49 -07:00
Samuel Newman 46a97fadcb Delete unused modals and invite code stuff (#8244)
* delete waitlist modal, which doesn't exist

* delete invite code modal

* rip out copied invites persisted state

* delete invite query

* delete unused modal implementations
2025-09-23 11:45:51 -07:00
Samuel Newman e90cfdc849 Modernise list create/edit dialog (#8223) 2025-09-23 20:11:21 +03:00
Samuel Newman 3bc906b911 fix profile feed liked by tablet offset (#9066) 2025-09-23 20:06:11 +03:00
Samuel Newman 7916c73b09 Better screen transitions for auth flow (#7803) 2025-09-23 20:04:22 +03:00
Samuel Newman 6d85fe05d1 Constraint video max height to 14/9 (#8611)
* constraint video max height to 14/9

* Apply new default to web video embed too

* Retain web handling

* Rename prop for clarity

* Align no-crop handling on native/web

* make it always constrained

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-09-23 07:24:31 -07:00
Samuel Newman be324432bd add sentry patch for vendoring metro import (#9065) 2025-09-23 07:16:19 -07:00
Eric Bailey 133bc2921e Fix incorrect usage of useAnimatedScrollHandler (#9063)
* Explicitly declare worklets for useAnimatedScrollHandler_FIXED.web.ts

* Delete unused web hook

* delete other platform split file, import directly

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-09-23 02:12:53 -07:00
pfrazee b90f807416 Nightly source-language update 2025-09-23 02:34:57 +00:00
Austin McKinley fbb85e109c Merge pull request #9064 from bluesky-social/migrate-ogcard
Normalize ogcard path metrics
2025-09-22 16:57:38 -07:00
Austin McKinley 9d6f51ff88 normalize ogcard metrics 2025-09-22 16:42:40 -07:00
Austin McKinley 7b10dcf249 Merge pull request #9061 from bluesky-social/migrate-ogcard
add metrics middleware to ogcard service
2025-09-22 15:12:36 -07:00
Austin McKinley 93f90bec97 fix metrics types 2025-09-22 11:56:57 -07:00
Austin McKinley 31eb41d21b remove bogus timeout 2025-09-22 11:45:02 -07:00
Austin McKinley 336e7b5372 update yarn.lock 2025-09-22 11:42:27 -07:00
Austin McKinley 9dcd8f81db update ogcard workflow to build and push new ECS images on PR creation 2025-09-22 11:38:10 -07:00
hailey cbcce8c960 improve og profile card (#9051) 2025-09-22 11:05:49 -07:00
Barry McGee 2c6752f24a Fix notification indicator appearing behind navigation text (#9035)
* Fix peeking notification indicator on web

* Remove relative positioning from LeftNav component
2025-09-22 10:49:16 -07:00
Austin McKinley 14ee7920f4 edit dockerfile to match 2025-09-22 10:28:17 -07:00
Austin McKinley 0ddd09eb9b add metrics middleware to ogcard service 2025-09-22 10:20:05 -07:00
Samuel Newman 5211b26903 Expo 54 (#8931)
* update deps

* update patches

* fix new arch optout

* build from source, disable new arch

* update to preview.14

* upgrade off of preview to release

* fix lockfile lint

* update patches

* add back expo-location

* downgrade reanimated

* downgrade pagerview

* use expo-file-system/legacy

* try and upgrade types/react-dom

* fix type of uint8array

* rm @types/react resolution from bad rebase

* more type errors

* fix mock

* bump rn-compressor

* add patch of https://github.com/bluesky-social/react-native-paste-input/pull/5

* delete NativeDropdown & remove Zeego

* `import React` in expo modules

* bump cocoapods

* rm unused picker lib

* add speculative react-native-mmkv patch

* revert patch, add temp debug info to error

* bring back speculative patch, patch built JS code instead of source

* revert patch, just do debugging part

* dep update

* update resolutions, dynamic-app-icon

* bump node version in eas.json

* update build instructions

* rm atob from docs

* Delete react-native-mmkv+2.12.2.patch

* bump expo which might include fix

* update expo patch version

* bump expo patch version

* wrong version oops

* update expo to latest

* update react-native-compressor, rm patch
2025-09-22 08:52:07 -07:00
Samuel Newman 6983efee7f iOS 26 with design compatibility mode (#9059)
* enable design compat mode

* patch compressor lib

https://github.com/numandev1/react-native-compressor/issues/367
2025-09-22 08:21:20 -07:00
Caidan ac71ea235f improve: enhance post OpenGraph metadata with engagement data and auth handling (#9046) 2025-09-17 14:30:10 -07:00
Samuel Newman 5e78c947a0 Add missing passive feed interactions (#9043) 2025-09-15 03:02:37 -07:00
pfrazee 952abc82f4 Nightly source-language update 2025-09-12 02:33:47 +00:00
surfdude29 b9a41f30a5 Generate separate strings for join messages in Newskie dialog (#8007)
* try again

* prettier

* prettier round 2

* Update src/components/NewskieDialog.tsx

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-09-11 12:08:30 -07:00
Samuel Newman 6a4c56dfa9 Log cleanup (#9029)
* change warn to log

* networkerror check on failed to check unread notifications

* rm both logs

* tweak imports
2025-09-11 11:23:16 -07:00
hailey 836d16fec9 ensure mod service header present for appeals (#9025) 2025-09-11 00:31:17 -07:00
pfrazee 58ffe365ae Nightly source-language update 2025-09-11 02:36:03 +00:00
jim 426f025940 Merge pull request #9023 from internet-development/caidanw/app-1040-feed-open-graph-tags
feat: add feed open graph tags
2025-09-10 14:19:15 -07:00
Samuel Newman 20ff3e3ba0 Ensure there is a thumbnail for videos on web (#9019) 2025-09-10 18:49:44 +03:00
Samuel Newman 5b8631d188 Fix profile lists/feeds/starterpacks tabs position issue (#8935) 2025-09-10 18:49:04 +03:00
Samuel Newman 5be753ecdf add prevent default to thread anchor checkmark (#9020) 2025-09-10 17:44:19 +03:00
Caidan Williams d7c180db0e style: remove trailing spaces 2025-09-09 20:35:14 -07:00
Caidan Williams f5079b8158 feat: add OpenGraph metadata for feed URLs in bskyweb
Enable rich link previews when feed URLs are shared in iMessage, Slack, and other social platforms. Adds feed title, description, creator info, and avatar images to improve sharing experience.
2025-09-09 20:03:50 -07:00
pfrazee c7591ef058 Nightly source-language update 2025-09-10 02:33:45 +00:00
Samuel Newman 372e20efa3 ALF saved feeds screen (#8844) 2025-09-09 22:40:25 +03:00
Samuel Newman 9e8bceec60 reduce checkmark size in thread anchor (#8942) 2025-09-09 10:27:16 -07:00
Samuel Newman 6432667f60 ALF lists screen (#8941)
* alf list screens

* relocate to `#/screens`, balkanize

* use useBreakpoints

* showCancel on subscribe menu

* fix typo
2025-09-09 10:20:33 -07:00
Samuel Newman 4a1b1f17f4 Clean up dialogs (#8934) 2025-09-09 18:45:36 +03:00
Samuel Newman 7574a745d1 Fix focus ring shapes (#9005)
* fix user avis being the wrong shape

* fix the fab being the wrong shape
2025-09-09 08:40:19 -07:00
Samuel Newman 53e43a957c run yarn lint --fix (#9013) 2025-09-09 08:38:21 -07:00
Samuel Newman bb30b592d7 Remove atob polyfill (#9000)
* remove atob polyfill

* remove base64-js as a dependency
2025-09-09 08:33:41 -07:00
Samuel Newman bbc55eb0d7 clean up language before suggesting (#9006) 2025-09-09 08:33:23 -07:00
Samuel Newman 3e2c181c40 Upgrade @types/react to 19 and run codemod (attempt 2) (#8918)
* update dependencies

* rm `import type React from 'react'`

* run codemods

* patch discord types

* update types/react-dom

* Update yarn.lock
2025-09-09 08:09:10 -07:00
pfrazee d496a22352 Nightly source-language update 2025-09-09 02:37:07 +00:00
Samuel Newman d8413b09f8 remove link from saved count (#9010) 2025-09-08 23:14:19 +03:00
Samuel Newman 5ec6db5297 bump version (#9008) 2025-09-08 11:31:14 -07:00
kindgracekind 2b4da7f536 Pass feed context to video feed post controls (#9004) 2025-09-08 02:24:07 -07:00
Eric Bailey 5cd4a3362d 1.108.0 Release Prep (#8993)
* Pull and extract

* Pull and extract part II
2025-09-05 13:14:20 -05:00
estrattonbailey 834486c97d Nightly source-language update 2025-09-05 16:28:09 +00:00
Eric Bailey e073bdac6e Remove region for AA call, actually not helpful (#8992) 2025-09-05 11:07:37 -05:00
Samuel Newman 73d72814c5 patch in unreleased upstream fix for jitter (#8952) 2025-09-05 08:39:55 -07:00
Samuel Newman daed047bb4 [Perf] Drawer gesture perf fix + related cleanup (#8953)
* split drawer layout into own component

* don't put props in dep array

* memoize pager view
2025-09-05 08:39:28 -07:00
Samuel Newman ee3e083938 Restore quick language select (#8981)
* restore quick language select

* rm showCancel

* rm margin from thread button

* alf composer icons

* stop hiding keyboard

* use trans
2025-09-05 08:34:00 -07:00
Eric Bailey 0f089060d2 Make logs more clear (#8991) 2025-09-05 10:25:42 -05:00
Samuel Newman 01ba0d0ed6 Revert "use comment instead of context (#8987)" (#8990)
This reverts commit c7f8da8f97.
2025-09-05 08:11:56 -07:00
Samuel Newman c7f8da8f97 use comment instead of context (#8987) 2025-09-05 18:03:29 +03:00
Eric Bailey 76cd645a40 Provide more precise data for age assurance (#8989) 2025-09-05 10:02:24 -05:00
Samuel Newman 124cfb57c7 only show bookmarks nux to existing users (#8988) 2025-09-05 09:49:34 -05:00
Samuel Newman ac0c4ef2d9 ensure headers are set when chat reporting (#8985) 2025-09-05 09:33:27 -05:00
Samuel Newman 917256713f require auth for bookmarks (#8983) 2025-09-05 09:31:25 -05:00
Samuel Newman c26906e15c add translation context for "saved" (#8982) 2025-09-05 07:29:57 -07:00
Samuel Newman 0c4b908020 Simplify post number formatting (#8978)
* revert number formatting change

* use formatPostStatCount in repost web
2025-09-05 09:29:10 -05:00
Samuel Newman 457cd3d0fb E2E fixes (#8980)
* update dev-env

* hide nux after onboarding

* use getter/setter for proxy header
2025-09-05 09:27:18 -05:00
estrattonbailey bc827dc97f Nightly source-language update 2025-09-05 00:40:16 +00:00
Eric Bailey c129108b78 108 fixes (#8977)
* Translation comment

* Fix error handling in starter pack generation

* Allow access to DM settings for age restricted users

* Leave post stat unit formatting up to translators
2025-09-04 19:36:23 -05:00
estrattonbailey 0b480bdaf8 Nightly source-language update 2025-09-04 22:33:00 +00:00
Eric Bailey 535d4d6cf7 📓 Bookmarks (#8976)
* Add button to controls, respace

* Hook up shadow and mutation

* Add Bookmarks screen

* Build out Bookmarks screen

* Handle removals via shadow

* Use truncateAndInvalidate strategy

* Add empty state

* Add toasts

* Add undo buttons to toasts

* Stage NUX, needs image

* Finesse post controls

* New reply icon

* Use curvier variant of repost icon

* Prevent layout shift with align_start

* Update api pkg

* Swap in new image

* Limit spacing on desktop

* Rm decimals over 10k

* Better optimistic adding/removing

* Add metrics

* Comment

* Remove unused code block

* Remove debug limit

* Fork shadow for web/native

* Tweak alt

* add preventExpansion: true

* Refine hitslop

* Add count to anchor

* Reduce space in compact mode

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-09-04 17:30:15 -05:00
estrattonbailey 04b869714e Nightly source-language update 2025-09-04 20:11:21 +00:00
surfdude29 9738f4858d Tweak location-related strings and labels (#8975)
* tweak string in BlockedGeoOverlay.tsx

* tweak string in AgeAssuranceAccountCard.tsx

* tweak string and labels in DeviceLocationRequestDialog.tsx

* prettier

* add missing `.` in DeviceLocationRequestDialog.tsx
2025-09-04 15:08:23 -05:00
estrattonbailey 0dafc2ccd6 Nightly source-language update 2025-09-04 16:11:27 +00:00
Eric Bailey f8ae0540a0 Provide geo-gated users optional GPS fallback for precise location data (#8973) 2025-09-04 11:07:12 -05:00
Alex Benzer 625b4e61db Welcome modal on logged-out homepage (#8944)
* Adds welcome modal to logged-out homepage

* Adds metrics and feature gate for welcome modal

* Slightly smaller text for mobile screens to avoid wrapping

* Remove unused SVG

* Adds text gradient and "X" close button

* Fix color on "Already have an account?" text

* tweak hooks, react import

* rm stylesheet

* use hardcoded colors

* add focus guards and scope

* no such thing as /home

* reduce spacign

* use css animations

* use session storage

* fix animation fill mode

* add a11y props

* Fix link/button color mismatch, reduce gap between buttons, show modal until user dismisses it

* Fix "Already have an account?" line left-aligning in small window sizes

* Adds "dismissed" and "presented" metric events

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-09-04 07:20:46 -07:00
Samuel Newman 0b02d9d9a7 bump version (#8970) 2025-09-04 07:16:52 -07:00
pfrazee 07d4ff4e8c Nightly source-language update 2025-09-04 02:34:11 +00:00
hailey 6c8e9fd8c9 add cursor constraint to safelink (#8966) 2025-09-03 08:55:09 -07:00
jim fd7b1292b8 Merge pull request #8923 from internet-development/@APiligrim/update-gif-select
Update: search on Gif select component to add small padding to match other search experiences
2025-09-02 22:21:16 -07:00
jim 135f37eae6 Merge pull request #8962 from internet-development/caidanw/app-1327-remove-pinned-post-from-replies-tab-on-profile
[APP-1327] Remove pinned post from replies tab on profiles
2025-09-02 22:19:15 -07:00
Eric Bailey 125191fc20 1.107.0 Prep Take 2 (#8964)
* Pull down latest translations

* Extract
2025-09-02 22:02:12 -05:00
Eric Bailey 566e869802 Use Automatic representation for all image pickers (#8963) 2025-09-02 21:43:33 -05:00
pfrazee c99500f5d3 Nightly source-language update 2025-09-03 02:33:35 +00:00
hailey bec0329774 enable link redirection in message (#8961) 2025-09-02 16:31:37 -07:00
Samuel Newman 670995a649 update bad token error message (#8954) 2025-09-02 18:27:52 -05:00
Caidan Williams b2ecaf9aa7 fix(feed): only include pins for author threads filter
Previously, pins were included for both 'posts_with_replies' and
'posts_and_author_threads' filters. This change restricts pin inclusion
to only the 'posts_and_author_threads' filter, ensuring correct feed
behavior and reducing unnecessary data in other filter modes.
2025-09-02 15:48:28 -07:00
hailey 01c466a213 fix safelink package.json (#8959) 2025-09-02 14:07:22 -07:00
Samuel Newman 75f0ee8a1e Tweaks to add to starter pack dialog (#8957) 2025-09-03 00:01:36 +03:00
hailey 5ce4aa5139 rm unknown fields (#8958) 2025-09-02 13:43:44 -07:00
hailey acdc509630 safelink (#8917)
Co-authored-by: hailey <me@haileyok.com>
Co-authored-by: Stanislas Signoud <signez@stanisoft.net>
Co-authored-by: will berry <wsb@wills-MBP.attlocal.net>
Co-authored-by: BlueSkiesAndGreenPastures <will@blueskyweb.xyz>
Co-authored-by: Chenyu Huang <itschenyu@gmail.com>
2025-09-02 13:36:20 -07:00
Samuel Newman b2258fb6cb Add QR code to github action (#8948) 2025-09-01 17:56:21 -07:00
Bartosz Kaszubowski 0bda727e8b Web: fix Suggested Accounts tabs end of scroll detection (#8939) 2025-09-01 03:23:41 -07:00
Anastasiya Uraleva 3601933ff6 move background to the parent and remove unused view 2025-08-29 17:40:41 -07:00
Samuel Newman 1c78f36a51 v1.107.0 release prep (#8938)
* yarn intl:pull

* yarn intl:extract:all
2025-08-29 08:24:43 -07:00
Samuel Newman d6dc52b6ea Improve e2e tests (#8927)
* get e2e image picker working

* verify create account actually reaches onboarding

* wait for image to actually be attached before posting

* wait until login finishes before moving on

* sign out before switch accounts then wait until logged in

* disable onboarding experiments in e2e

* add testId to handle availability checkmark

* fix too long username

* update thread muting test to reflect current behaviour

* hackfix for the british english translation

* unflake the onboarding tests

* fix curate list flow

* admit defeat on the most list one
2025-08-29 08:15:05 -07:00
Samuel Newman 541502c7ee prevent double border (#8937) 2025-08-29 07:02:28 -07:00
Samuel Newman 0114cfcfe1 fix hook being marked as async for no reason (#8932) 2025-08-29 10:36:20 +03:00
pfrazee 6172423714 Nightly source-language update 2025-08-29 02:36:45 +00:00
Samuel Newman 8a4398608a Close web mention suggestions popup on Escape (#8605)
* alf web typeahead

* fix type error

* fix escape behaviour

* change selection on hover

* rm React.

* undo random change
2025-08-28 17:03:12 -07:00
Samuel Newman 27c1058568 use Link instead of manual navigation (#8930) 2025-08-28 15:18:56 -07:00
Eric Bailey 2f2b542e5c Bump expo-image-picker to 17.0.5 (#8929)
* Bump picker, add patch for next version

* Revert "Bump picker, add patch for next version"

This reverts commit a4aab0bdcd.

* Update to latest picker@next
2025-08-28 15:14:28 -07:00
Eric Bailey 2a54781ce0 Update dev env (#8921)
* Update dev-env

* Integrate appviewDid value from dev-env

* Use correct env value to disable policy update overlay

* Remove log
2025-08-28 04:20:06 -07:00
Anastasiya Uraleva cc4acae758 Update search on Gif select component to add small padding to match other search experiences 2025-08-27 21:31:29 -07:00
APiligrim eeeeda3495 Nightly source-language update 2025-08-27 21:23:05 -07:00
pfrazee dcba5c4668 Nightly source-language update 2025-08-28 02:37:01 +00:00
Chenyu 7469e7306c Update birthday setting dialog (#8910)
* update birthday setting dialog

* Update src/components/dialogs/BirthDateSettings.tsx

Co-authored-by: Eric Bailey <git@esb.lol>

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-08-27 19:55:18 -05:00
surfdude29 58c2e68a24 Update FollowDialog.tsx (#8919) 2025-08-28 02:43:33 +03:00
surfdude29 01c20958c1 Add english-only-resource context and tweak labels (#8805)
* add context in VerificationsDialog.tsx

* add context in VerifierDialog.tsx

* add context in VerificationSettings.tsx

* add context in EditProfileDialog.tsx

* add context and tweak label in ChangeHandleDialog.tsx

* tweak label in ContentHider.tsx
2025-08-27 18:12:01 -05:00
jim 99722436b1 Merge pull request #8885 from internet-development/caidanw/app-1409-add-data-nosnippet-to-discover-feed
feat: prevent search engines from using Discover feed content as page descriptions
2025-08-27 14:56:40 -07:00
Caidan Williams 4a1f1efd76 refactor(feed): simplify isDiscoverFeed logic in FeedPage 2025-08-27 11:58:58 -07:00
Caidan Williams bd730db03e refactor(feed): remove NoSnippetWrapper from FeedPage in favor of dataSet attribute
Replaces NoSnippetWrapper with a dataSet prop on the root View to
control the nosnippet attribute for discover feeds. This simplifies the
component structure and improves maintainability by reducing unnecessary
wrappers.
2025-08-27 11:58:58 -07:00
Samuel Newman 8a22584a87 prevent android exposures to gate (#8916) 2025-08-27 10:12:09 -07:00
mozzius 17cbf75747 Nightly source-language update 2025-08-27 16:08:25 +00:00
Samuel Newman 8adebe6702 starter pack dialog tweaks (#8913) 2025-08-27 09:01:10 -07:00
Samuel Newman 39d460db51 Language select final tweaks (#8914)
* [APP-1303] Redesign/refactor post language select (#8884)

* Nightly source-language update

* Nightly source-language update

* [APP-1303] Redesign/refactor post language select

* update: stylesheets.create to use the latest structure

* update styles to modern structure

* update: dialog breakpoints on web and delete depricated language modals

* remove unused post languages settings dialog

* restructure Post languages dialog

* place the Dialog.Close inside the Dialog.ScrollableInner

* add: language search

* update search and language variables for clarity

* fix: memoize language state lists

* chore: add comments

* update proper colors to the background

* add back older error boundary

* add: tweaks to the mobile and web responsiveness

* add tweaks to center the container

* update labels

* update button and border

* added translation updates

* Update: text input to reuse search input

* remove unused file

* update: web breakpoints

* run eslint and prettier

---------

Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com>
Co-authored-by: Anastasiya Uraleva <anastasiya@Anastasiyas-MacBook-Pro.local>
Co-authored-by: Anastasiya Uraleva <anastasiya@Mac.localdomain>

* rm old file

* sort out styles, add FlatListFooter component

* rm cancel button in favor of search input X

* get dialog height working on iOS

* delete `DropdownButton`

* hide scroll indicators on android

* ios scroll indicator insets

* get footer sorta working on android

* change button color on press

* rm empty file

---------

Co-authored-by: Anastasiya Uraleva <anastasiyauraleva@gmail.com>
Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com>
Co-authored-by: Anastasiya Uraleva <anastasiya@Anastasiyas-MacBook-Pro.local>
Co-authored-by: Anastasiya Uraleva <anastasiya@Mac.localdomain>
2025-08-27 09:00:36 -07:00
Samuel Newman eac0290143 Add suggested follows experiment to onboarding (#8847)
* add new gated screen to onboarding

* add tab bar, adjust layout

* replace chevron with arrow

* get suggested accounts working on native

* tweaks for web

* add metrics to follow all

* rm non-functional link from card

* ensure selected interests are passed through to interests query

* fix logcontext

* followed all accounts! toast

* rm save interests function

* Update src/screens/Onboarding/StepSuggestedAccounts/index.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* use admonition

* rm comment

* Better interest tabs (#8879)

* make tabs draggable

* move tab component to own file

* rm focused state from tab, improve label

* add focus styles, remove focus when dragging

* rm log

* add arrows to tabs

* rename Tabs -> InterestTabs

* try and simplify approach

* rename ref

* Update InterestTabs.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Update src/components/InterestTabs.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Update src/components/ProgressGuide/FollowDialog.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Update src/components/ProgressGuide/FollowDialog.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* add newline

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* fix flex problem

* Add value proposition screen experiment (#8898)

* add assets

* add value prop experiment

* add alt text

* add metrics

* add transitions

* add skip button

* tweak copy

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* add borderless variant for web

* rm pointer events

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Add slight delay, prevent layout shift

* Handle layout shift, add Let's Go! text

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
2025-08-27 04:17:45 -07:00
APiligrim 349c70e413 Nightly source-language update 2025-08-26 20:21:22 -07:00
APiligrim 203ccbb1fb Nightly source-language update 2025-08-26 20:21:22 -07:00
pfrazee 0617fca5ef Nightly source-language update 2025-08-27 02:36:23 +00:00
hailey a1ed59e0ee use an env var for configurable proxy header (#8863) 2025-08-26 17:13:11 -07:00
surfdude29 39c6984c6e Mark label for translation in LoggedOutCTA.tsx (#8908)
* Mark label for translation in `LoggedOutCTA.tsx`

* Update LoggedOutCTA.tsx
2025-08-26 15:09:47 -07:00
Eric Bailey 9f7c920346 Fix toast type (#8909)
* Fix confusing toast API

* Provide all exports to e2e file

* Fix first usage in Composer

* Loosen type, add Trans tag
2025-08-26 16:54:43 -05:00
Caidan Williams 6ddc268b91 feat: prevent search engines from indexing Discover feed content as page descriptions 2025-08-26 13:01:36 -07:00
Caidan Williams 0517768cd3 feat: add data-nosnippet attribute to Discover feed for SEO control 2025-08-26 13:01:36 -07:00
Bartosz Kaszubowski 0555d3623c Fix RightNav elements focus outline on web (#8788) 2025-08-26 14:43:25 -05:00
Bartosz Kaszubowski 8dcf1825ec Web: fix Edit Profile discard warning when pressing backdrop (#8824)
* Web: fix Edit Profile discard warning

* cleanup imports
2025-08-26 14:38:59 -05:00
surfdude29 c12fd4482e Discover FeedsDiscover New Feeds (#8860) 2025-08-26 14:34:34 -05:00
kindgracekind 88e6dff484 Enable show less / more buttons for third party feeds (#8672)
Co-authored-by: hailey <hailey@blueskyweb.xyz>
Co-authored-by: Hailey <me@haileyok.com>
2025-08-26 12:16:50 -07:00
Samuel Newman 98d96bd28b Modernise change password dialog (#8269)
* alf change password dialog

* rm old modal

* move dialog

* fix buttons on native

* lowercase

* fix dupe import

* Apply suggestions from code review

Thanks @surfdude29 :)

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* use primary_subtle, change web layout

* move to into non-network err, warn -> error

* error -> safeMessage

* better message than token is invalid

* cancel button native only

* move close to end of focus priority

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2025-08-26 10:50:39 -07:00
Eric Bailey dd2830ba6c Fix bad copy pasta (#8907) 2025-08-26 12:33:43 -05:00
Eric Bailey 6a16ef83f6 Fix default opengraph (#8872)
* Fix default opengraph

* Add avatar back

* Remove default og images
2025-08-26 11:32:08 -05:00
Eric Bailey 8ec20026c0 Yeah toast (#8878)
* Split out into macro component

* Add Action component

* Add fallback

* add button to view post after sending

* Dismiss toast when clicking action button

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-08-26 11:20:04 -05:00
Alex Benzer acd7211b35 Adds signup CTA tests for logged-out visitors (#8906) 2025-08-26 08:52:38 -07:00
Samuel Newman b70e5b2f38 Add verification checkmarks to embed.bsky.app (#8644)
* update vite+typescript

* update atproto api to latest, split out utils

* add checkmark to post

* add checkie to embed

* revert change to example post

* fix ext link color
2025-08-26 08:24:46 -07:00
Inbestigator 5a074fa37a Add profile link to switcher menu (#8867)
* feat(leftnav): add profile link to switcher menu

* fix: close menu on navigate
2025-08-26 08:18:40 -07:00
Eric Bailey df20ae237e Threads v2 cleanup (#8902)
* Delete root PostThread component

* Remove PostThreadItem, migrate DebugMod to use new components

* Remove other unused components

* Move PostThreadFollowBtn to new home

* Move PostThreadComposePrompt to new home

* Remove gate

* Keep naming in DebugMod

* rm v1 prefs

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-08-26 09:54:19 -05:00
surfdude29 e91a683810 Tweak SelectMediaButton error string, accessibilityHint, and comments (#8870)
* remove isNative check from accessibilityHint

* tweak grammar in error string

* prettier

* tweak comment
2025-08-26 05:58:04 -07:00
jim bc8e3ba30b Merge pull request #8806 from internet-development/binaryfiddler/starter-pack-part2
Starter pack dialog implementations
2025-08-25 23:23:20 -07:00
jim e4c5fb7de5 Merge pull request #8901 from internet-development/caidanw/app-1358-final-card-refresh
feat(ui): redesign suggested profiles 'See more' card
2025-08-25 23:19:11 -07:00
jim d1aab56982 Merge pull request #8900 from internet-development/caidanw/app-1413-clean-up-canonical-urls
feat: implement canonical URL filter to clean parameters for SEO
2025-08-25 23:07:00 -07:00
pfrazee bb1eab41c6 Nightly source-language update 2025-08-26 02:38:40 +00:00
Caidan Williams eb45154823 refactor: rename canonical filter to canonicalize_url for better clarity
- Rename filter from 'canonical' to 'canonicalize_url' to follow Pongo2
naming conventions
- Update function name from filterCanonical to filterCanonicalizeURL
- Update template usage in post.html and profile.html
- Update test function name and all references
2025-08-25 17:18:15 -07:00
Caidan Williams 2b4d32bb09 feat(ui): redesign "See more" suggested profiles card
Refactored the "See more accounts you might like" card to use ButtonIcon
and ButtonText for improved consistency and accessibility. Updated
styles for better alignment, spacing, and visual hierarchy. Navigates to
the Explore page when pressed.
2025-08-25 16:57:33 -07:00
Caidan Williams b667e8b985 refactor(feed): rename Arrow to ArrowRight for clarity 2025-08-25 16:16:55 -07:00
Samuel Newman e8cd7ab131 Remove handle suggestion gate (#8899)
* remove handle suggestion gate

* rm from list of gates
2025-08-25 16:13:54 -07:00
Caidan Williams 685650b296 test: add comprehensive coverage for canonical URL filter
- Verify clean URL passthrough behavior
- Test query parameter removal functionality
- Test fragment removal functionality
- Test combined query params and fragments handling
- Ensure graceful degradation for malformed URLs
2025-08-25 16:04:45 -07:00
Caidan Williams f9118b2729 feat: apply canonical filter to profile template URLs for SEO
Use canonical filter on requestURI in canonical link tag while preserving
original URL with parameters for og:url metadata. This provides clean
canonical URLs for search engines while maintaining full Open Graph URLs.
2025-08-25 16:04:45 -07:00
Caidan Williams 5c04c9e3e3 feat: apply canonical filter to clean URLs in post template
Uses canonical filter for canonical link tags while preserving original URLs with parameters for og:url metadata, improving SEO through proper URL canonicalization
2025-08-25 16:04:45 -07:00
Caidan Williams 60a1be6de0 feat(seo): add canonical URL filter to remove query parameters
Addresses community feedback about canonical URLs being misleading
when they include UTM parameters. The new Pongo2 filter creates
clean canonical URLs while preserving tracking parameters for
social sharing.
2025-08-25 16:04:45 -07:00
Samuel Newman f3905c574b remove root sibling parent(s??) from composer (#8897) 2025-08-25 15:30:13 -07:00
Samuel Newman 27c591f031 Fix Gif Select dialog search bar scrolling away (#8874)
* fix flatlist dialogs on web

* tweak gif select dialog, add style prop to textfield
2025-08-25 09:37:12 -07:00
Samuel Newman 7771b56a43 fix searching for phrases by replacing smart quotes (#8865) 2025-08-25 19:25:28 +03:00
Samuel Newman 3810b29fb6 ALF log screen (#8845) 2025-08-25 19:25:02 +03:00
Samuel Newman 9d77b361fe add content hider padding (#8843) 2025-08-25 19:24:48 +03:00
Samuel Newman 54f7af12ca Update DM header to match new Layout style (#8846) 2025-08-25 19:23:08 +03:00
pfrazee ecb77a9b96 Nightly source-language update 2025-08-25 02:40:42 +00:00
Samuel Newman 45825dd005 Fix alt text in media preview (#8854) 2025-08-24 14:31:42 +03:00
Samuel Newman 5e07b0c77d Skip dirty git tree check in prebuild (#8853) 2025-08-24 14:31:25 +03:00
pfrazee dddea126f4 Nightly source-language update 2025-08-24 02:42:01 +00:00
Samuel Newman ce2604d0e9 Update @mozzius/expo-dynamic-app-icon (#8834) 2025-08-24 01:18:02 +03:00
Eric Bailey c28195067f Update origin (#8889) 2025-08-23 15:33:31 -05:00
Eric Bailey 3b7b93a411 Debug (#8888)
* Swap URL

* Add debug
2025-08-23 14:45:57 -05:00
jim 99d7570818 Merge pull request #8883 from internet-development/caidanw/app-1408-add-canonical-url
feat: add canonical URLs to improve SEO
2025-08-22 19:40:11 -07:00
pfrazee 7b9bc52720 Nightly source-language update 2025-08-23 02:36:29 +00:00
Chenyu Huang f84a756691 optimize for the web 2025-08-22 17:59:54 -07:00
jim 78e1e26a2c Merge pull request #8882 from internet-development/binaryfiddler/isnetwork
wrapp failed to send feed interactions inside isNetworkError
2025-08-22 16:10:53 -07:00
Caidan Williams d8c904d1d9 feat: add canonical URL to profile template for better SEO 2025-08-22 14:42:05 -07:00
Caidan Williams 560c503aa6 feat: add canonical URL to post template for better SEO 2025-08-22 14:40:57 -07:00
Caidan Williams 5120c037d1 feat: add canonical URL to home page for SEO optimization 2025-08-22 14:39:56 -07:00
Chenyu Huang 184df9cbfe wrapp failed to send feed interactions inside isNetworkError 2025-08-22 13:28:32 -07:00
Eric Bailey 912ab1bd9b [LEG-246] Geo overlay (#8881)
* Add AgeBlockedGeo

* Add MaxMind usage text

* Add geo overlay

---------

Co-authored-by: rafael <rafael@blueskyweb.xyz>
2025-08-22 14:55:16 -05:00
surfdude29 f038ac70da rearrange translation logic (#8880) 2025-08-22 12:00:00 -07:00
jim e404ddfbcd Merge pull request #8875 from internet-development/caidan/app-1406-follow-suggestions-card-to-show-follow-back
[APP-1406] follow suggestions card to show follow back
2025-08-21 22:15:07 -07:00
pfrazee 0baa5198e0 Nightly source-language update 2025-08-22 02:38:34 +00:00
Caidan Williams 7a1adfe67c refactor: all uses of "Follow Back" to "Follow back" 2025-08-21 15:55:01 -07:00
jim af55e578b2 Merge pull request #8876 from internet-development/caidanw/app-1399-trending-videos-icon-missing
[APP-1399] trending videos icon missing
2025-08-21 15:44:14 -07:00
Caidan Williams 994003e603 fix: capitalization of "Follow back" to "Follow Back" 2025-08-21 13:53:44 -07:00
Caidan Williams 8ab49da6da refactor: replace View with Button in ViewMoreCard for consistent colors across themes
Updated ViewMoreCard to use a Button component instead of a styled View
for the 'View more' action. This improves accessibility and consistency
with other interactive elements in TrendingVideos.
2025-08-21 13:23:53 -07:00
Caidan Williams c097f68a7d refactor: extract ViewMoreCard from VideoCards component
Moved the 'View more' card logic into a separate ViewMoreCard component
for better separation of concerns and readability.

Updated imports to use named React hooks instead of React namespace. To
modernise this old component per the new guidelines.
2025-08-21 12:53:35 -07:00
Caidan Williams b1ad229771 fix: show 'Follow back' label if user is followed by profile
Updated ProfileCard.FollowButton label to display 'Follow back' when the
current user is followed by the profile being viewed.
2025-08-21 12:29:08 -07:00
Caidan eabcd9150d [APP-1357] profile header follow recommendations (#8784) 2025-08-21 21:56:17 +03:00
Chenyu Huang 962654041c fix linting, minor ui tweaks 2025-08-21 11:49:08 -07:00
pfrazee d900d0b7a7 Nightly source-language update 2025-08-21 02:38:38 +00:00
Chenyu Huang 0474a3b6f7 address feedback 2025-08-20 15:00:06 -07:00
Eric Bailey 16701392b9 [APP-1398] Fix post shadow/like shadow state when replying (#8866)
* Add optimisticReplyCount to post shadow

* Add special util

* Fix subtle bug in query cache
2025-08-20 15:21:52 -05:00
Chenyu Huang f7c05c2df4 use Dialog.InnerFlatlist 2025-08-20 12:31:12 -07:00
cirx ef7c222e1b fix typo (#8869) 2025-08-20 11:11:02 -07:00
surfdude29 9835d0fb7b tweak plural formatting (#8871) 2025-08-20 11:10:34 -07:00
pfrazee bf6e01551e Nightly source-language update 2025-08-20 02:38:26 +00:00
Chenyu Huang 68e0a78f70 loading state 2025-08-19 16:37:49 -07:00
Chenyu Huang e32f280f47 clean up onSuccess callback 2025-08-19 15:59:12 -07:00
Chenyu Huang f42b5831bb parameterize the initial profile for starter pack profile select wizard screen 2025-08-19 15:28:37 -07:00
Eric Bailey ea7f984a40 Fix isPartOfLastBranchFromDepth assignment (#8856) 2025-08-19 11:33:46 -05:00
Ridley Combs 4923786927 [Lightbox] prevent browser-default escape key behavior (#8859)
Some browsers exit fullscreen when the escape key is pressed without `preventDefault()` being called; we don't want that when we're handling the press ourselves.
2025-08-19 06:25:42 -07:00
pfrazee 48baf7277d Nightly source-language update 2025-08-19 02:39:11 +00:00
Eric Bailey 92ad887e38 Add underage notice to Moderation screen (#8868)
* Add underage notice to Moderation screen

* Incorporate suggestion

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2025-08-18 19:24:54 -05:00
Chenyu Huang 7182cd3d5e starter pack dialog flow from profileMenu 2025-08-16 19:45:43 -07:00
907 changed files with 136833 additions and 118099 deletions
+9
View File
@@ -16,6 +16,9 @@ EXPO_PUBLIC_LOG_LEVEL=debug
# Enable debug logs for specific logger instances
EXPO_PUBLIC_LOG_DEBUG=session
# Bluesky appview DID
EXPO_PUBLIC_BLUESKY_PROXY_DID=
# Chat service DID
EXPO_PUBLIC_CHAT_PROXY_DID=
@@ -30,3 +33,9 @@ EXPO_PUBLIC_SENTRY_DSN=
# Bitdrift API key. If undefined, Bitdrift will be disabled.
EXPO_PUBLIC_BITDRIFT_API_KEY=
# bapp-config web worker URL
BAPP_CONFIG_DEV_URL=
# Dev-only passthrough value for bapp-config web worker
BAPP_CONFIG_DEV_BYPASS_SECRET=
+5 -1
View File
@@ -34,8 +34,10 @@ module.exports = {
'P',
'Admonition',
'Admonition.Admonition',
'Toast.Action',
'AgeAssuranceAdmonition',
'Span',
'StackedButton',
],
impliedTextProps: [],
suggestedTextWrappers: {
@@ -87,7 +89,7 @@ module.exports = {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{argsIgnorePattern: '^_', varsIgnorePattern: '^_'},
{argsIgnorePattern: '^_', varsIgnorePattern: '^_.+'},
],
'@typescript-eslint/consistent-type-imports': [
'warn',
@@ -110,6 +112,8 @@ module.exports = {
'bskyembed',
'src/locale/locales/_build/',
'src/locale/locales/**/*.js',
'*.e2e.ts',
'*.e2e.tsx',
],
settings: {
componentWrapperFunctions: ['observer'],
@@ -1,6 +1,11 @@
name: build-and-push-link-aws
on:
workflow_dispatch:
pull_request:
paths:
- 'bskylink/**'
- 'Dockerfile.bskylink'
- '.github/workflows/build-and-push-link-aws.yaml'
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
@@ -45,7 +50,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: true
file: ./Dockerfile.bskylink
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
@@ -1,6 +1,11 @@
name: build-and-push-ogcard-aws
on:
workflow_dispatch:
pull_request:
paths:
- 'bskyogcard/**'
- 'Dockerfile.bskyogcard'
- '.github/workflows/build-and-push-ogcard-aws.yaml'
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
@@ -45,7 +50,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: true
file: ./Dockerfile.bskyogcard
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+10 -5
View File
@@ -50,8 +50,8 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
distribution: "temurin"
java-version: "17"
- name: ⚙️ Install dependencies
run: yarn install
@@ -96,7 +96,7 @@ jobs:
id: timestamp
uses: nanzm/get-time-action@master
with:
format: 'MM-DD-HH-mm-ss'
format: "MM-DD-HH-mm-ss"
- name: 🚀 Upload Production Artifact
id: upload-artifact-production
@@ -146,6 +146,11 @@ jobs:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: 🧹 Clear Metro cache
if: ${{ inputs.profile == 'production' }}
# https://github.com/expo/eas-cli/issues/2959#issuecomment-2749791326
run: rm -rf ${TMPDIR:-/tmp}/metro-cache
- name: 🏗️ Build Production APK
if: ${{ inputs.profile == 'production' }}
run: >
@@ -182,11 +187,11 @@ jobs:
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
if: ${{ inputs.profile == 'testflight' }}
if: ${{ inputs.profile == 'testflight-android' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ inputs.profile == 'testflight' }}
if: ${{ inputs.profile == 'testflight-android' }}
run: echo ${{ github.sha }} > most-recent-testflight-commit.txt
+24 -5
View File
@@ -15,7 +15,7 @@ jobs:
build:
if: github.repository == 'bluesky-social/social-app'
name: Build and Submit iOS
runs-on: macos-15
runs-on: macos-26
steps:
- name: Check for EXPO_TOKEN
run: >
@@ -53,12 +53,12 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.4'
xcode-version: "26.0"
- name: ☕️ Setup Cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: 1.14.3
version: 1.16.2
- name: 💾 Cache Pods
uses: actions/cache@v3
@@ -98,10 +98,29 @@ jobs:
yarn use-build-number-with-bump
eas build -p ios
--profile ${{ inputs.profile || 'testflight' }}
--local --output build.ipa --non-interactive
--local --output build.tar.gz --non-interactive
- name: 📂 Extract build artifact
run: |
if [ -f "build.tar.gz" ]; then
echo "Extracting build.tar.gz..."
mkdir ios-build
tar -xzf build.tar.gz -C ios-build
echo "Extraction completed successfully"
else
echo "Archive file not found!"
exit 1
fi
- name: 🚀 Deploy
run: eas submit -p ios --non-interactive --path build.ipa
run: eas submit -p ios --non-interactive --path ios-build/ios/build/Bluesky.ipa
- name: 🪲 Upload dSYM to Sentry
run: >
SENTRY_ORG=blueskyweb
SENTRY_PROJECT=app
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
yarn sentry-cli debug-files upload ios-build/ios/build/Bluesky.app.dSYM.zip --include-sources
- name: 📚 Get version from package.json
id: get-build-info
@@ -150,7 +150,7 @@ jobs:
# GitHub actions are horrible so let's just copy paste this in
buildIfNecessaryIOS:
name: Build and Submit iOS
runs-on: macos-15
runs-on: macos-26
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-ios
cancel-in-progress: true
@@ -192,12 +192,12 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2'
xcode-version: "26.0"
- name: ☕️ Setup Cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: 1.14.3
version: 1.16.2
- name: 💾 Cache Pods
uses: actions/cache@v3
@@ -293,8 +293,8 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
distribution: "temurin"
java-version: "17"
- name: ⚙️ Install dependencies
run: yarn install
@@ -332,7 +332,7 @@ jobs:
id: timestamp
uses: nanzm/get-time-action@master
with:
format: 'MM-DD-HH-mm-ss'
format: "MM-DD-HH-mm-ss"
- name: 🚀 Upload Artifact
id: upload-artifact
+6 -4
View File
@@ -84,10 +84,10 @@ jobs:
repo: context.repo.repo,
pull_number: ${{ github.event.issue.number }}
});
console.log(`PR HEAD SHA: ${pr.data.head.sha}`);
console.log(`PR HEAD REF: ${pr.data.head.ref}`);
core.setOutput('head-sha', pr.data.head.sha);
core.setOutput('head-ref', pr.data.head.ref);
@@ -188,12 +188,14 @@ jobs:
header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }}
number: ${{ github.event.issue.number }}
message: |
Your requested OTA deployment was successful! You may now apply it by opening the deep link below in your browser:
Your requested OTA deployment was successful! You may now apply it by either scanning the QR code or opening the deep link below in your browser:
<img src="https://bsky-qr.vercel.app?channel=pull-request-${{ github.event.issue.number }}" width=300 height=300>
`bluesky://intent/apply-ota?channel=pull-request-${{ github.event.issue.number }}`
---
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
- name: 💬 Drop a comment
uses: marocchino/sticky-pull-request-comment@v2
+2 -1
View File
@@ -26,8 +26,9 @@ COPY --from=build /app /app
RUN mkdir /app/data && chown node /app/data
VOLUME /app/data
EXPOSE 3000
EXPOSE 3000 3001
ENV CARD_PORT=3000
ENV CARD_METRICS_PORT=3001
ENV NODE_ENV=production
# potential perf issues w/ io_uring on this version of node
ENV UV_USE_IO_URING=0
+2
View File
@@ -70,6 +70,8 @@ Bluesky is an open social network built on the AT Protocol, a flexible technolog
See [./LICENSE](./LICENSE) for the full license.
Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge).
## P.S.
We ❤️ you and all of the ways you support us. Thank you for making Bluesky a great place!
+9 -3
View File
@@ -3,20 +3,26 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: ?users
SERVER_PATH: ?users
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
# Post an image with the porn label
- assertVisible:
id: "composeFAB"
id: "composeFAB"
- tapOn:
id: "composeFAB"
- inputText: "Post with an image"
- tapOn:
id: "openGalleryBtn"
id: "openMediaBtn"
- extendedWaitUntil:
visible:
id: "selectedPhotosView"
- tapOn: "Content warnings"
- tapOn: "Porn"
- tapOn:
+18 -5
View File
@@ -3,13 +3,17 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: ?users
SERVER_PATH: ?users
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- assertVisible:
id: "composeFAB"
id: "composeFAB"
- tapOn:
id: "composeFAB"
- inputText: "Post text only"
@@ -21,7 +25,10 @@ appId: xyz.blueskyweb.app
id: "composeFAB"
- inputText: "Post with an image"
- tapOn:
id: "openGalleryBtn"
id: "openMediaBtn"
- extendedWaitUntil:
visible:
id: "selectedPhotosView"
- tapOn:
id: "composerPublishBtn"
- assertVisible:
@@ -46,7 +53,10 @@ appId: xyz.blueskyweb.app
id: "replyBtn"
- inputText: "Reply with an image"
- tapOn:
id: "openGalleryBtn"
id: "openMediaBtn"
- extendedWaitUntil:
visible:
id: "selectedPhotosView"
- tapOn:
id: "composerPublishBtn"
- assertVisible:
@@ -73,7 +83,10 @@ appId: xyz.blueskyweb.app
id: "quoteBtn"
- inputText: "QP with an image"
- tapOn:
id: "openGalleryBtn"
id: "openMediaBtn"
- extendedWaitUntil:
visible:
id: "selectedPhotosView"
- tapOn:
id: "composerPublishBtn"
- assertVisible:
+9 -3
View File
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: ""
SERVER_PATH: ""
- runFlow:
file: ../setupApp.yml
- tapOn:
@@ -25,13 +25,19 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "passwordInput"
- inputText: "hunter22"
- hideKeyboard
- tapOn:
# hideKeyboard on ios is borked
# https://docs.maestro.dev/troubleshooting/known-issues#hidekeyboard-command-is-flaky
text: "Your account"
- tapOn:
id: "nextBtn"
- tapOn:
text: "Not Now"
optional: true
- inputText: "e2e-test"
- extendedWaitUntil:
visible:
id: "handleAvailableCheck"
- tapOn:
id: "nextBtn"
- assertVisible: "Give your profile a face"
+26 -38
View File
@@ -3,95 +3,83 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users&follows&posts"
SERVER_PATH: "?users&follows&posts"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
label: "Create a curate list"
id: "e2eGotoLists"
- tapOn:
id: "newUserListBtn"
- assertVisible:
id: "createOrEditListModal"
- waitForAnimationToEnd
- assertVisible: "Create user list"
- tapOn:
id: "editNameInput"
id: "editListNameInput"
- inputText: "Good Ppl"
- tapOn:
id: "editDescriptionInput"
id: "editListDescriptionInput"
- inputText: "They good"
- tapOn: "Save"
- tapOn: "Save"
- assertNotVisible:
id: "createOrEditListModal"
- assertNotVisible: "Create user list"
- tapOn: "People"
- assertVisible: "Good Ppl"
- assertVisible: "They good"
- tapOn:
label: "Edit display name and description via the edit curatelist modal"
point: "90%,9%"
id: "moreOptionsBtn"
- tapOn: "Edit list details"
- assertVisible:
id: "createOrEditListModal"
- assertVisible: "Edit user list"
- tapOn:
id: "editNameInput"
id: "editListNameInput"
- eraseText
- inputText: "Bad Ppl"
- hideKeyboard
- tapOn:
id: "editDescriptionInput"
id: "editListDescriptionInput"
- eraseText
- inputText: "They bad"
- tapOn: "Save"
- tapOn: "Save"
- assertNotVisible:
id: "createOrEditListModal"
- assertNotVisible: "Edit user list"
- assertVisible: Bad Ppl
- assertVisible: They bad
- tapOn:
label: "Remove description via the edit curatelist modal"
point: "90%,9%"
id: "moreOptionsBtn"
- tapOn: "Edit list details"
- assertVisible:
id: "createOrEditListModal"
- assertVisible: "Edit user list"
- tapOn:
id: "editDescriptionInput"
id: "editListDescriptionInput"
- eraseText
- tapOn: "Save"
- tapOn: "Save"
- assertNotVisible:
id: "createOrEditListModal"
- assertNotVisible: "Edit user list"
- assertNotVisible:
id: "listDescription"
- tapOn:
label: "Delete the curatelist"
point: "90%,9%"
id: "moreOptionsBtn"
- tapOn: "Delete List"
- tapOn:
id: "confirmBtn"
- tapOn:
label: "Create a new curatelist"
id: "e2eGotoLists"
- assertVisible:
id: "newUserListBtn"
- tapOn:
id: "newUserListBtn"
- assertVisible:
id: "createOrEditListModal"
- assertVisible: "Create user list"
- tapOn:
id: "editNameInput"
id: "editListNameInput"
- inputText: "Good Ppl"
- tapOn:
id: "editDescriptionInput"
id: "editListDescriptionInput"
- inputText: "They good"
- tapOn: "Save"
- tapOn: "Save"
- assertNotVisible:
id: "createOrEditListModal"
- assertNotVisible: "Create user list"
- tapOn: "People"
- assertVisible: "Good Ppl"
- assertVisible: "They good"
@@ -146,7 +134,7 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "bottomBarSearchBtn"
- tapOn: "Search for posts, users, or feeds"
- tapOn: "Search for posts, users[,]? or feeds"
- inputText: "bob"
- tapOn:
id: "searchAutoCompleteResult-bob.test"
+9 -13
View File
@@ -3,11 +3,14 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users&follows&labels"
SERVER_PATH: "?users&follows&labels"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
# create a modlist
- tapOn:
@@ -16,32 +19,25 @@ appId: xyz.blueskyweb.app
id: "moderationlistsBtn"
- tapOn: "New list"
- tapOn:
id: "editNameInput"
id: "editListNameInput"
- inputText: "Muted Users"
- tapOn:
id: "editDescriptionInput"
id: "editListDescriptionInput"
- inputText: "Shhh"
- tapOn: "Save"
- tapOn: "Save"
# view modlist
- assertVisible: "Muted Users"
- assertVisible: "Shhh"
- tapOn:
label: "Dropdown"
point: "71%,9%"
- tapOn: "Subscribe to this list"
- tapOn: "Mute accounts"
- tapOn: "Mute list"
- tapOn: "Unmute"
- tapOn:
label: "Dropdown"
point: "71%,9%"
- tapOn: "Subscribe to this list"
- tapOn: "Block accounts"
- tapOn: "Block list"
- tapOn: "Unblock"
# the rest of the behaviors are tested in curate-lists.yml
# the rest of the behaviors are tested in curate-lists.yml
+23 -7
View File
@@ -3,11 +3,15 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users"
SERVER_PATH: "?users"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "e2eStartOnboarding"
- tapOn: "Open avatar creator"
@@ -21,12 +25,24 @@ appId: xyz.blueskyweb.app
- tapOn: "Select the atom emoji as your avatar"
- tapOn: "Done"
- waitForAnimationToEnd
- tapOn: "Continue to next step"
- tapOn:
id: "onboardingContinue"
- assertVisible: "What are your interests?"
- tapOn:
label: "Tap on continue"
point: "50%,92%"
- assertVisible: "You're ready to go!"
id: "onboardingContinue"
- assertVisible: "Suggested for you"
# mock server doesn't have suggestions api
- tapOn: "Skip to next step"
- swipe:
direction: "LEFT"
- swipe:
direction: "LEFT"
- assertVisible: "Complete onboarding and start using your account"
- tapOn:
label: "Tap on Lets go"
point: "50%,92%"
id: "onboardingFinish"
- tapOn:
label: "NUX, if applicable"
text: "Close"
optional: true
- waitForAnimationToEnd
- assertVisible: "Following"
+25 -8
View File
@@ -3,27 +3,44 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users"
SERVER_PATH: "?users"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "e2eStartOnboarding"
- tapOn: "Select an avatar"
- waitForAnimationToEnd
- assertVisible: "Search your library…"
- assertVisible: "Photos"
- assertVisible: "Collections"
- tapOn:
point: "50%,22%"
- waitForAnimationToEnd
- tapOn: "Done"
- waitForAnimationToEnd
- tapOn: "Continue to next step"
- tapOn:
id: "onboardingContinue"
- assertVisible: "What are your interests?"
- tapOn:
label: "Tap on continue"
point: "50%,92%"
- assertVisible: "You're ready to go!"
id: "onboardingContinue"
- assertVisible: "Suggested for you"
# mock server doesn't have suggestions api
- tapOn: "Skip to next step"
- swipe:
direction: "LEFT"
- swipe:
direction: "LEFT"
- assertVisible: "Complete onboarding and start using your account"
- tapOn:
label: "Tap on Lets go"
point: "50%,92%"
id: "onboardingFinish"
- tapOn:
label: "NUX, if applicable"
text: "Close"
optional: true
- waitForAnimationToEnd
- assertVisible: "Following"
+4 -1
View File
@@ -3,11 +3,14 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users&follows&posts"
SERVER_PATH: "?users&follows&posts"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "postDropdownBtn"
+4 -4
View File
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users&posts&feeds"
SERVER_PATH: "?users&posts&feeds"
- runFlow:
file: ../setupApp.yml
- tapOn:
@@ -12,10 +12,10 @@ appId: xyz.blueskyweb.app
# Navigate to another user profile
- extendedWaitUntil:
visible:
id: "bottomBarSearchBtn"
id: "bottomBarSearchBtn"
- tapOn:
id: "bottomBarSearchBtn"
- tapOn: "Search for posts, users, or feeds"
- tapOn: "Search for posts, users[,]? or feeds"
- inputText: "b"
- tapOn:
id: "searchAutoCompleteResult-bob.test"
@@ -38,4 +38,4 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "profileHeaderDropdownBtn"
- tapOn: "Unmute Account"
- assertNotVisible: "Account Muted"
- assertNotVisible: "Account Muted"
+6 -4
View File
@@ -3,20 +3,22 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users"
SERVER_PATH: "?users"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
# Navigate to another user profile via autocomplete
- extendedWaitUntil:
visible:
id: "bottomBarSearchBtn"
- tapOn:
id: "bottomBarSearchBtn"
- assertVisible: "Search for posts, users, or feeds"
- tapOn: "Search for posts, users, or feeds"
- assertVisible: "Search for posts, users[,]? or feeds"
- tapOn: "Search for posts, users[,]? or feeds"
- inputText: "b"
- tapOn:
id: "searchAutoCompleteResult-bob.test"
- assertVisible:
id: "profileView"
+5 -1
View File
@@ -8,8 +8,12 @@ appId: xyz.blueskyweb.app
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- assertVisible:
id: "storybookBtn"
id: "storybookBtn"
- tapOn:
id: "storybookBtn"
- tapOn:
+42 -9
View File
@@ -3,13 +3,16 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users&follows"
SERVER_PATH: "?users&follows"
- runFlow:
file: ../setupApp.yml
# Login, create a thread, and log out
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- assertVisible:
id: "composeFAB"
- tapOn:
@@ -19,8 +22,15 @@ appId: xyz.blueskyweb.app
id: "composerPublishBtn"
# Login, reply to the thread, and log out
- tapOn:
id: "e2eSignOut"
- tapOn:
id: "e2eSignInBob"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "replyBtn"
- tapOn:
id: "replyBtn"
- inputText: "Reply 1"
@@ -28,8 +38,13 @@ appId: xyz.blueskyweb.app
id: "composerPublishBtn"
# Login, confirm notification exists, mute thread, and log out
- tapOn:
id: "e2eSignOut"
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "bottomBarNotificationsBtn"
- assertVisible:
@@ -39,18 +54,25 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "postDropdownBtn"
childOf:
id: "postThreadItem-by-bob.test"
id: "postThreadItem-by-bob.test"
- tapOn: "Mute thread"
# Login, reply to the thread twice, and log out
- tapOn:
id: "e2eSignOut"
- tapOn:
id: "e2eSignInBob"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "bottomBarProfileBtn"
- tapOn:
id: "profilePager-selector-1"
- tapOn:
id: "replyBtn"
- tapOn:
id: "replyBtn"
- inputText: "Reply 2"
- tapOn:
id: "composerPublishBtn"
@@ -60,24 +82,35 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "composerPublishBtn"
# Login, confirm notifications dont exist, unmute the thread, confirm notifications exist
# Login, confirm notifications dont exist, unmute the thread, ~~confirm notifications exist~~
# Mute thread behaviour no longer change old notifications after muting/unmuting a thread -sfn
- tapOn:
id: "e2eSignOut"
- tapOn:
id: "e2eSignInAlice"
- extendedWaitUntil:
visible:
id: "viewHeaderHomeFeedPrefsBtn"
- tapOn:
id: "bottomBarNotificationsBtn"
- assertNotVisible:
- assertVisible: ".*Reply 1.*"
- assertNotVisible: ".*Reply 2.*"
- assertNotVisible: ".*Reply 3.*"
- assertVisible:
id: "feedItem-by-bob.test"
- tapOn:
id: "bottomBarHomeBtn"
id: "feedItem-by-bob.test"
- tapOn:
id: "postDropdownBtn"
childOf:
id: "postThreadItem-by-bob.test"
- tapOn: "Unmute thread"
- tapOn:
id: "bottomBarNotificationsBtn"
- swipe:
from:
id: "notifsFeed"
id: "notifsFeed"
direction: DOWN
- assertVisible:
id: "feedItem-by-bob.test"
- assertVisible: ".*Reply 1.*"
- assertNotVisible: ".*Reply 2.*"
- assertNotVisible: ".*Reply 3.*"
+23 -14
View File
@@ -1,7 +1,7 @@
import {createServer as createHTTPServer} from 'node:http'
import {parse} from 'node:url'
import {createServer, TestPDS} from '../jest/test-pds'
import {createServer, type TestPDS} from '../jest/test-pds'
async function main() {
let server: TestPDS
@@ -181,10 +181,10 @@ async function main() {
'warn-profile',
'warn-posts',
'muted-account',
'muted-by-list-account',
'muted-by-list-acc',
'blocking-account',
'blockedby-account',
'mutual-block-account',
'mutual-block-acc',
]) {
await server.mocker.createUser(user)
await server.mocker.follow('alice', user)
@@ -411,16 +411,16 @@ async function main() {
await server.mocker.addToMuteList(
'alice',
list,
server.mocker.users['muted-by-list-account'].did,
server.mocker.users['muted-by-list-acc'].did,
)
await server.mocker.createPost('muted-by-list-account', 'muted post')
await server.mocker.createPost('muted-by-list-acc', 'muted post')
await server.mocker.createQuotePost(
'muted-by-list-account',
'muted-by-list-acc',
'account quote post',
anchorPost,
)
await server.mocker.createReply(
'muted-by-list-account',
'muted-by-list-acc',
'account reply',
anchorPost,
)
@@ -470,16 +470,16 @@ async function main() {
)
await server.mocker.createPost(
'mutual-block-account',
'mutual-block-acc',
'mutual-block post',
)
await server.mocker.createQuotePost(
'mutual-block-account',
'mutual-block-acc',
'mutual-block quote post',
anchorPost,
)
await server.mocker.createReply(
'mutual-block-account',
'mutual-block-acc',
'mutual-block reply',
anchorPost,
)
@@ -488,15 +488,15 @@ async function main() {
repo: server.mocker.users.alice.did,
},
{
subject: server.mocker.users['mutual-block-account'].did,
subject: server.mocker.users['mutual-block-acc'].did,
createdAt: new Date().toISOString(),
},
)
await server.mocker.users[
'mutual-block-account'
'mutual-block-acc'
].agent.app.bsky.graph.block.create(
{
repo: server.mocker.users['mutual-block-account'].did,
repo: server.mocker.users['mutual-block-acc'].did,
},
{
subject: server.mocker.users.alice.did,
@@ -509,7 +509,16 @@ async function main() {
}
}
console.log('Ready')
return res.writeHead(200).end(server.pdsUrl)
return res
.writeHead(200, {
'content-type': 'application/json',
})
.end(
JSON.stringify({
pdsUrl: server.pdsUrl,
appviewDid: server.appviewDid,
}),
)
} catch (e) {
console.error('Error!', e)
return res.writeHead(500).end()
+9 -4
View File
@@ -1,13 +1,18 @@
appId: xyz.blueskyweb.app
---
- launchApp:
appId: "xyz.blueskyweb.app"
clearState: true
appId: "xyz.blueskyweb.app"
clearState: true
- waitForAnimationToEnd
- tapOn: "http://localhost:8081"
- waitForAnimationToEnd
- extendedWaitUntil:
visible: "Continue"
- swipe:
from: "Bluesky"
direction: DOWN
from: "Bluesky"
direction: DOWN
duration: 100
- tapOn:
id: e2eProxyHeaderInput
- inputText: ${output.result}
- pressKey: Enter
+5 -2
View File
@@ -1,5 +1,8 @@
// eslint-disable-next-line
http.post('http://localhost:1986/' + SERVER_PATH, {
// eslint-disable-next-line no-undef
var res = http.post('http://localhost:1986/' + SERVER_PATH, {
headers: {'Content-Type': 'text/plain'},
body: '',
})
// eslint-disable-next-line no-undef
output.result = json(res.body).appviewDid
+1 -1
View File
@@ -1,4 +1,4 @@
import {createDownloadResumable, deleteAsync} from 'expo-file-system'
import {createDownloadResumable, deleteAsync} from 'expo-file-system/legacy'
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
import {
+39 -4
View File
@@ -40,9 +40,10 @@ module.exports = function (_config) {
runtimeVersion: {
policy: 'appVersion',
},
icon: './assets/app-icons/ios_icon_default_light.png',
icon: './assets/app-icons/ios_icon_default_next.png',
userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
newArchEnabled: false,
ios: {
supportsTablet: false,
bundleIdentifier: 'xyz.blueskyweb.app',
@@ -102,6 +103,7 @@ module.exports = function (_config) {
'zh-Hans',
'zh-Hant',
],
UIDesignRequiresCompatibility: true,
},
associatedDomains: ASSOCIATED_DOMAINS,
entitlements: {
@@ -110,6 +112,34 @@ module.exports = function (_config) {
'com.apple.security.application-groups': 'group.app.bsky',
},
privacyManifests: {
NSPrivacyCollectedDataTypes: [
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypeCrashData',
NSPrivacyCollectedDataTypeLinked: false,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: [
'NSPrivacyCollectedDataTypePurposeAppFunctionality',
],
},
{
NSPrivacyCollectedDataType:
'NSPrivacyCollectedDataTypePerformanceData',
NSPrivacyCollectedDataTypeLinked: false,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: [
'NSPrivacyCollectedDataTypePurposeAppFunctionality',
],
},
{
NSPrivacyCollectedDataType:
'NSPrivacyCollectedDataTypeOtherDiagnosticData',
NSPrivacyCollectedDataTypeLinked: false,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: [
'NSPrivacyCollectedDataTypePurposeAppFunctionality',
],
},
],
NSPrivacyAccessedAPITypes: [
{
NSPrivacyAccessedAPIType:
@@ -141,7 +171,7 @@ module.exports = function (_config) {
barStyle: 'light-content',
},
android: {
icon: './assets/app-icons/android_icon_default_light.png',
icon: './assets/app-icons/android_icon_default_next.png',
adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-foreground.png',
@@ -207,13 +237,12 @@ module.exports = function (_config) {
{
ios: {
deploymentTarget: '15.1',
newArchEnabled: false,
buildReactNativeFromSource: true,
},
android: {
compileSdkVersion: 35,
targetSdkVersion: 35,
buildToolsVersion: '35.0.0',
newArchEnabled: false,
},
},
],
@@ -303,6 +332,11 @@ module.exports = function (_config) {
android: './assets/app-icons/android_icon_default_dark.png',
prerendered: true,
},
next: {
ios: './assets/app-icons/ios_icon_default_next.png',
android: './assets/app-icons/android_icon_default_next.png',
prerendered: true,
},
/**
* Bluesky+ core set
@@ -355,6 +389,7 @@ module.exports = function (_config) {
},
],
['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
['expo-location'],
].filter(Boolean),
extra: {
eas: {
Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M9.7 16.895a4 4 0 0 1 4.6 0l3.7 2.6V6.5a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v12.995l3.7-2.6Zm10.3 2.6c0 1.62-1.825 2.567-3.15 1.636l-3.7-2.6a2 2 0 0 0-2.3 0l-3.7 2.6C5.825 22.062 4 21.115 4 19.495V6.5a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v12.995Z"/></svg>

After

Width:  |  Height:  |  Size: 334 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#405168" d="M14.2 2.625c.834 0 1.482 0 2.001.042.523.043.949.131 1.331.326a3.38 3.38 0 0 1 1.475 1.475c.195.382.283.807.326 1.33.042.52.042 1.168.042 2.002v11.09c0 .495 0 .893-.027 1.199-.028.301-.087.585-.26.809-.249.323-.63.518-1.037.533-.282.01-.547-.107-.808-.26-.265-.154-.588-.385-.991-.673l-3.54-2.528c-.36-.258-.461-.322-.559-.347a.6.6 0 0 0-.306 0c-.098.025-.199.09-.559.347l-3.54 2.528c-.403.288-.726.519-.991.674-.261.152-.526.269-.808.259a1.38 1.38 0 0 1-1.038-.534c-.172-.223-.231-.507-.259-.808a7 7 0 0 1-.024-.528l-.003-.67V7.8c0-.834 0-1.482.042-2.001.043-.523.13-.949.325-1.331a3.38 3.38 0 0 1 1.476-1.475c.382-.195.808-.283 1.33-.326.52-.042 1.168-.042 2.002-.042h4.4Zm-4.4.75c-.846 0-1.458 0-1.94.04-.477.039-.792.114-1.051.246A2.63 2.63 0 0 0 5.66 4.81c-.132.259-.208.574-.247 1.051-.04.482-.039 1.094-.039 1.94v11.09l.003.658c.003.186.01.34.021.473.025.267.07.37.106.418a.63.63 0 0 0 .472.243c.059.002.168-.022.4-.158.23-.133.52-.34.935-.636l3.54-2.529c.308-.22.543-.396.81-.464.222-.056.454-.056.676 0 .267.068.5.244.81.464l3.54 2.529c.414.296.704.503.933.636.233.137.343.16.402.158a.63.63 0 0 0 .472-.243c.036-.048.081-.15.106-.419.024-.263.024-.62.024-1.13V7.8c0-.846 0-1.458-.04-1.94-.039-.477-.114-.792-.246-1.051A2.63 2.63 0 0 0 17.19 3.66c-.259-.132-.575-.207-1.051-.246-.482-.04-1.094-.04-1.94-.04H9.8Zm4.056 4.238a.375.375 0 0 1 .53.53L12.53 10l1.857 1.856a.375.375 0 0 1-.53.53L12 10.53l-1.856 1.857a.375.375 0 0 1-.53-.53L11.47 10 9.613 8.144a.375.375 0 0 1 .53-.53L12 9.47l1.856-1.857Z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#006AFF" d="M16 2.5a4 4 0 0 1 4 4v12.995c0 1.62-1.825 2.567-3.15 1.636l-3.7-2.6a2 2 0 0 0-2.3 0l-3.7 2.6C5.825 22.062 4 21.115 4 19.495V6.5a4 4 0 0 1 4-4h8Z"/></svg>

After

Width:  |  Height:  |  Size: 250 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="M12.591 21.806h.002l.001-.002.006-.004.018-.014a10 10 0 0 0 .304-.235 26 26 0 0 0 3.333-3.196C18.048 16.29 20 13.305 20 10a8 8 0 1 0-16 0c0 3.305 1.952 6.29 3.745 8.355a26 26 0 0 0 3.333 3.196 16 16 0 0 0 .304.235l.018.014.006.004.002.002a1 1 0 0 0 1.183 0Zm-.593-9.306a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 449 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12 2a8 8 0 0 1 8 8c0 3.305-1.953 6.29-3.745 8.355a26 26 0 0 1-3.333 3.197q-.152.12-.237.184l-.067.05-.018.014-.005.004-.002.002h-.001c-.003-.004-.042-.055-.592-.806l.592.807a1 1 0 0 1-1.184 0v-.001l-.003-.002-.005-.004-.018-.014-.067-.05a24 24 0 0 1-1.066-.877 26 26 0 0 1-2.504-2.503C5.953 16.29 4 13.305 4 10a8 8 0 0 1 8-8Zm0 2a6 6 0 0 0-6 6c0 2.56 1.547 5.076 3.255 7.044A24 24 0 0 0 12 19.723a24 24 0 0 0 2.745-2.679C16.453 15.076 18 12.56 18 10a6 6 0 0 0-6-6Zm-.002 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Zm0 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"/></svg>

After

Width:  |  Height:  |  Size: 656 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#080B12" d="M20.002 7a2 2 0 0 0-2-2h-12a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2a1 1 0 0 1 1 1v1.918l3.375-2.7a1 1 0 0 1 .625-.218h5a2 2 0 0 0 2-2V7Zm2 8a4 4 0 0 1-4 4h-4.648l-4.727 3.781A1.001 1.001 0 0 1 7.002 22v-3h-1a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8Z"/></svg>

After

Width:  |  Height:  |  Size: 355 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#080B12" d="M22.002 15a4 4 0 0 1-4 4h-4.648l-4.727 3.781A1.001 1.001 0 0 1 7.002 22v-3h-1a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8Z"/></svg>

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

+26
View File
@@ -0,0 +1,26 @@
## Run / Test
Install dependencies:
```bash
cd bskyembed
yarn
```
Run the dev server:
```bash
yarn dev
```
You can see the embed homepage at http://localhost:5173
### Testbed
In another terminal window, run the snippet dev script:
```bash
yarn dev-snippet
```
You can then see the testbed page at http://localhost:5173/test
+12 -9
View File
@@ -4,26 +4,29 @@
"type": "module",
"scripts": {
"dev": "vite",
"dev-snippet": "tsc --project tsconfig.snippet.json && serve -s dist -p 3000 -n",
"build": "tsc && vite build",
"build-snippet": "tsc --project tsconfig.snippet.json",
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src"
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@atproto/api": "0.13.6",
"@preact/preset-vite": "^2.8.2",
"@vitejs/plugin-legacy": "^5.3.2",
"preact": "^10.4.8",
"terser": "^5.30.3"
"@atproto/api": "^0.15.25",
"preact": "^10.4.8"
},
"devDependencies": {
"@preact/preset-vite": "^2.10.2",
"@vitejs/plugin-legacy": "^7.0.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.19.0",
"eslint-config-preact": "^1.3.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"postcss": "^8.4.38",
"serve": "^14.2.5",
"tailwindcss": "^3.4.3",
"typescript": "^5.5.4",
"vite": "^5.2.8",
"vite-tsconfig-paths": "^4.3.2"
"terser": "^5.43.1",
"typescript": "^5.8.3",
"vite": "^7.0.4",
"vite-tsconfig-paths": "^5.1.4"
}
}
+11 -1
View File
@@ -3,9 +3,19 @@ interface Window {
bluesky: {
scan: (element?: Pick<Element, 'querySelectorAll'>) => void
}
BSKY_DEV_EMBED_URL?: string
}
const EMBED_URL = 'https://embed.bsky.app'
/**
* Allow url to be overwritten during development
*/
const IS_DEV =
window.location.protocol === 'file:' ||
window.location.hostname === 'localhost'
const EMBED_URL =
IS_DEV && window.BSKY_DEV_EMBED_URL
? window.BSKY_DEV_EMBED_URL
: 'https://embed.bsky.app'
window.bluesky = window.bluesky || {
scan,
+1 -1
View File
@@ -49,7 +49,7 @@ export function Container({
}
}}>
{href && <Link href={href} />}
<div className="flex-1 px-4 pt-3 pb-2.5">{children}</div>
<div className="flex-1 px-4 pt-3 pb-2.5 max-w-full">{children}</div>
</div>
)
}
+33 -11
View File
@@ -17,8 +17,11 @@ import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg'
import playIcon from '../../assets/play_filled_corner2_rounded.svg'
import starterPackIcon from '../../assets/starterPack.svg'
import {CONTENT_LABELS, labelsToInfo} from '../labels'
import {getRkey} from '../utils'
import * as bsky from '../types/bsky'
import {getRkey} from '../util/rkey'
import {getVerificationState} from '../util/verification-state'
import {Link} from './link'
import {VerificationCheck} from './verification-check'
export function Embed({
content,
@@ -75,23 +78,35 @@ export function Embed({
CONTENT_LABELS.includes(label.val),
)
const verification = getVerificationState({profile: record.author})
return (
<Link
href={`/profile/${record.author.did}/post/${getRkey(record)}`}
className="transition-colors hover:bg-neutral-100 dark:hover:bg-slate-700 border dark:border-slate-600 rounded-xl p-2 gap-1.5 w-full flex flex-col">
<div className="flex gap-1.5 items-center">
<div className="w-4 h-4 overflow-hidden rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0">
<div className="w-4 h-4 rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0">
<img
className="rounded-full"
src={record.author.avatar}
style={isAuthorLabeled ? {filter: 'blur(1.5px)'} : undefined}
/>
</div>
<p className="line-clamp-1 text-sm">
<span className="font-bold">{record.author.displayName}</span>
<span className="text-textLight dark:text-textDimmed ml-1">
<div className="flex flex-1 items-center shrink min-w-0 min-h-0">
<p className="block text-sm shrink-0 font-bold max-w-[70%] line-clamp-1">
{record.author.displayName?.trim() || record.author.handle}
</p>
{verification.isVerified && (
<VerificationCheck
className="ml-[3px] mt-px shrink-0 self-center"
verifier={verification.role === 'verifier'}
size={12}
/>
)}
<p className="block line-clamp-1 text-sm text-textLight dark:text-textDimmed shrink-[10] ml-1">
@{record.author.handle}
</span>
</p>
</p>
</div>
</div>
{text && <p className="text-sm">{text}</p>}
{record.embeds?.map(embed => (
@@ -315,7 +330,7 @@ function ExternalEmbed({
{content.external.thumb && (
<img
src={content.external.thumb}
className="aspect-[1.91/1] object-cover"
className="aspect-[1200/630] object-cover"
/>
)}
<div className="py-3 px-4">
@@ -404,7 +419,12 @@ function StarterPackEmbed({
}: {
content: AppBskyGraphDefs.StarterPackViewBasic
}) {
if (!AppBskyGraphStarterpack.isRecord(content.record)) {
if (
!bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
content.record,
AppBskyGraphStarterpack.isRecord,
)
) {
return null
}
@@ -415,7 +435,7 @@ function StarterPackEmbed({
<Link
href={starterPackHref}
className="w-full rounded-xl overflow-hidden border dark:border-slate-600 flex flex-col items-stretch">
<img src={imageUri} className="aspect-[1.91/1] object-cover" />
<img src={imageUri} className="aspect-[1200/630] object-cover" />
<div className="py-3 px-4">
<div className="flex space-x-2 items-center">
<img src={starterPackIcon} className="w-10 h-10" />
@@ -443,7 +463,9 @@ function StarterPackEmbed({
}
// from #/lib/strings/starter-pack.ts
function getStarterPackImage(starterPack: AppBskyGraphDefs.StarterPackView) {
function getStarterPackImage(
starterPack: AppBskyGraphDefs.StarterPackViewBasic,
) {
const rkey = getRkey({uri: starterPack.uri})
return `https://ogcard.cdn.bsky.app/start/${starterPack.creator.did}/${rkey}`
}
+36 -14
View File
@@ -11,10 +11,15 @@ import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg'
import logo from '../../assets/logo.svg'
import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg'
import {CONTENT_LABELS} from '../labels'
import {getRkey, niceDate, prettyNumber} from '../utils'
import * as bsky from '../types/bsky'
import {niceDate} from '../util/nice-date'
import {prettyNumber} from '../util/pretty-number'
import {getRkey} from '../util/rkey'
import {getVerificationState} from '../util/verification-state'
import {Container} from './container'
import {Embed} from './embed'
import {Link} from './link'
import {VerificationCheck} from './verification-check'
interface Props {
thread: AppBskyFeedDefs.ThreadViewPost
@@ -28,16 +33,25 @@ export function Post({thread}: Props) {
)
let record: AppBskyFeedPost.Record | null = null
if (AppBskyFeedPost.isRecord(post.record)) {
if (
bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record,
AppBskyFeedPost.isRecord,
)
) {
record = post.record
}
const verification = getVerificationState({profile: post.author})
const href = `/profile/${post.author.did}/post/${getRkey(post)}`
return (
<Container href={href}>
<div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}>
<div className="flex gap-2.5 items-center cursor-pointer">
<Link href={`/profile/${post.author.did}`} className="rounded-full">
<div className="flex gap-2.5 items-center cursor-pointer w-full max-w-full">
<Link
href={`/profile/${post.author.did}`}
className="rounded-full shrink-0">
<div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0">
<img
src={post.author.avatar}
@@ -45,19 +59,27 @@ export function Post({thread}: Props) {
/>
</div>
</Link>
<div>
<div className="flex flex-1 flex-col min-w-0">
<div className="flex flex-1 items-center">
<Link
href={`/profile/${post.author.did}`}
className="block font-bold text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 text-ellipsis decoration-2">
{post.author.displayName?.trim() || post.author.handle}
</Link>
{verification.isVerified && (
<VerificationCheck
className="pl-[3px] mt-px shrink-0"
verifier={verification.role === 'verifier'}
size={15}
/>
)}
</div>
<Link
href={`/profile/${post.author.did}`}
className="font-bold text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 decoration-2">
<p>{post.author.displayName}</p>
</Link>
<Link
href={`/profile/${post.author.did}`}
className="text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1">
<p>@{post.author.handle}</p>
className="block text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1">
@{post.author.handle}
</Link>
</div>
<div className="flex-1" />
<Link
href={href}
className="transition-transform hover:scale-110 shrink-0 self-start">
@@ -133,7 +155,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) {
<Link
key={counter}
href={segment.link.uri}
className="text-blue-400 hover:underline"
className="text-blue-500 hover:underline"
disableTracking={
!segment.link.uri.startsWith('https://bsky.app') &&
!segment.link.uri.startsWith('https://go.bsky.app')
@@ -0,0 +1,56 @@
import {h} from 'preact'
type IconProps = {
size: number
className?: string
}
export function VerificationCheck({
verifier,
...rest
}: {verifier: boolean} & IconProps) {
return verifier ? <VerifierCheck {...rest} /> : <VerifiedCheck {...rest} />
}
export function VerifiedCheck({size, className}: IconProps) {
return (
<svg
fill="none"
viewBox="0 0 24 24"
width={size}
height={size}
className={className}>
<circle cx="12" cy="12" r="11.5" fill="hsl(211, 99%, 53%)" />
<path
fill="#fff"
fillRule="evenodd"
clipRule="evenodd"
d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z"
/>
</svg>
)
}
export function VerifierCheck({size, className}: IconProps) {
return (
<svg
fill="none"
viewBox="0 0 24 24"
width={size}
height={size}
className={className}>
<path
fill="hsl(211, 99%, 53%)"
fillRule="evenodd"
clipRule="evenodd"
d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z"
/>
<path
fill="#fff"
fillRule="evenodd"
clipRule="evenodd"
d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z"
/>
</svg>
)
}
+10 -3
View File
@@ -14,9 +14,11 @@ import {
import {Container} from '../components/container'
import {Link} from '../components/link'
import {Post} from '../components/post'
import {niceDate} from '../utils'
import * as bsky from '../types/bsky'
import {niceDate} from '../util/nice-date'
const DEFAULT_POST = 'https://bsky.app/profile/emilyliu.me/post/3jzn6g7ixgq2y'
const DEFAULT_POST =
'https://bsky.app/profile/did:plc:vjug55kidv6sye7ykr5faxxn/post/3jzn6g7ixgq2y'
const DEFAULT_URI =
'at://did:plc:vjug55kidv6sye7ykr5faxxn/app.bsky.feed.post/3jzn6g7ixgq2y'
@@ -222,7 +224,12 @@ function Snippet({
const snippet = useMemo(() => {
const record = thread.post.record
if (!AppBskyFeedPost.isRecord(record)) {
if (
!bsky.dangerousIsType<AppBskyFeedPost.Record>(
record,
AppBskyFeedPost.isRecord,
)
) {
return ''
}
+1 -1
View File
@@ -8,7 +8,7 @@ import {applyTheme, initSystemColorMode} from '../color-mode'
import {Container} from '../components/container'
import {Link} from '../components/link'
import {Post} from '../components/post'
import {getRkey} from '../utils'
import {getRkey} from '../util/rkey'
const root = document.getElementById('app')
if (!root) throw new Error('No root element')
+49
View File
@@ -0,0 +1,49 @@
import {ValidationResult} from '@atproto/lexicon'
export * as profile from './profile'
/**
* Fast type checking without full schema validation, for use with data we
* trust, or for non-critical path use cases. Why? Our SDK's `is*` identity
* utils do not assert the type of the entire object, only the `$type` string.
*
* For full validation of the object schema, use the `validate` export from
* this file.
*
* Usage:
* ```ts
* import * as bsky from '#/types/bsky'
*
* if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
* }
* ```
*/
export function dangerousIsType<R extends {$type?: string}>(
record: unknown,
identity: <V>(v: V) => v is V & {$type: NonNullable<R['$type']>},
): record is R {
return identity(record)
}
/**
* Fully validates the object schema, which has a performance cost.
*
* For faster checks with data we trust, like that from our app view, use the
* `dangerousIsType` export from this same file.
*
* Usage:
* ```ts
* import * as bsky from '#/types/bsky'
*
* if (bsky.validate(item, AppBskyFeedPost.validateRecord)) {
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
* }
* ```
*/
export function validate<R extends {$type?: string}>(
record: unknown,
validator: (v: unknown) => ValidationResult<R>,
): record is R {
return validator(record).success
}
+10
View File
@@ -0,0 +1,10 @@
import {type AppBskyActorDefs, type ChatBskyActorDefs} from '@atproto/api'
/**
* Matches any profile view exported by our SDK
*/
export type AnyProfileView =
| AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileView
| AppBskyActorDefs.ProfileViewDetailed
| ChatBskyActorDefs.ProfileViewBasic
+11
View File
@@ -0,0 +1,11 @@
export function niceDate(date: number | string | Date) {
const d = new Date(date)
return `${d.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})} at ${d.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
})}`
}
+152
View File
@@ -0,0 +1,152 @@
/**
* This file is a copy of what exists in the social-app
*/
import {
AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyEmbedVideo,
AppBskyFeedDefs,
AppBskyGraphDefs,
AppBskyLabelerDefs,
} from '@atproto/api'
export type Embed =
| {
type: 'post'
view: AppBskyEmbedRecord.ViewRecord
}
| {
type: 'post_not_found'
view: AppBskyEmbedRecord.ViewNotFound
}
| {
type: 'post_blocked'
view: AppBskyEmbedRecord.ViewBlocked
}
| {
type: 'post_detached'
view: AppBskyEmbedRecord.ViewDetached
}
| {
type: 'feed'
view: AppBskyFeedDefs.GeneratorView
}
| {
type: 'list'
view: AppBskyGraphDefs.ListView
}
| {
type: 'labeler'
view: AppBskyLabelerDefs.LabelerView
}
| {
type: 'starter_pack'
view: AppBskyGraphDefs.StarterPackViewBasic
}
| {
type: 'images'
view: AppBskyEmbedImages.View
}
| {
type: 'link'
view: AppBskyEmbedExternal.View
}
| {
type: 'video'
view: AppBskyEmbedVideo.View
}
| {
type: 'post_with_media'
view: Embed
media: Embed
}
| {
type: 'unknown'
view: null
}
export type EmbedType<T extends Embed['type']> = Extract<Embed, {type: T}>
export function parseEmbedRecordView({record}: AppBskyEmbedRecord.View): Embed {
if (AppBskyEmbedRecord.isViewRecord(record)) {
return {
type: 'post',
view: record,
}
} else if (AppBskyEmbedRecord.isViewNotFound(record)) {
return {
type: 'post_not_found',
view: record,
}
} else if (AppBskyEmbedRecord.isViewBlocked(record)) {
return {
type: 'post_blocked',
view: record,
}
} else if (AppBskyEmbedRecord.isViewDetached(record)) {
return {
type: 'post_detached',
view: record,
}
} else if (AppBskyFeedDefs.isGeneratorView(record)) {
return {
type: 'feed',
view: record,
}
} else if (AppBskyGraphDefs.isListView(record)) {
return {
type: 'list',
view: record,
}
} else if (AppBskyLabelerDefs.isLabelerView(record)) {
return {
type: 'labeler',
view: record,
}
} else if (AppBskyGraphDefs.isStarterPackViewBasic(record)) {
return {
type: 'starter_pack',
view: record,
}
} else {
return {
type: 'unknown',
view: null,
}
}
}
export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed {
if (AppBskyEmbedImages.isView(embed)) {
return {
type: 'images',
view: embed,
}
} else if (AppBskyEmbedExternal.isView(embed)) {
return {
type: 'link',
view: embed,
}
} else if (AppBskyEmbedVideo.isView(embed)) {
return {
type: 'video',
view: embed,
}
} else if (AppBskyEmbedRecord.isView(embed)) {
return parseEmbedRecordView(embed)
} else if (AppBskyEmbedRecordWithMedia.isView(embed)) {
return {
type: 'post_with_media',
view: parseEmbedRecordView(embed.record),
media: parseEmbed(embed.media),
}
} else {
return {
type: 'unknown',
view: null,
}
}
}
+9
View File
@@ -0,0 +1,9 @@
const formatter = new Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 1,
roundingMode: 'trunc',
})
export function prettyNumber(number: number) {
return formatter.format(number)
}
+6
View File
@@ -0,0 +1,6 @@
import {AtUri} from '@atproto/api'
export function getRkey({uri}: {uri: string}): string {
const at = new AtUri(uri)
return at.rkey
}
+31
View File
@@ -0,0 +1,31 @@
import * as bsky from '../types/bsky'
export type VerificationState = {
role: 'default' | 'verifier'
isVerified: boolean
}
export function getVerificationState({
profile,
}: {
profile?: bsky.profile.AnyProfileView
}): VerificationState {
if (!profile || !profile.verification) {
return {
role: 'default',
isVerified: false,
}
}
const {verifiedStatus, trustedVerifierStatus} = profile.verification
const isVerifiedUser = ['valid', 'invalid'].includes(verifiedStatus)
const isVerifierUser = ['valid', 'invalid'].includes(trustedVerifierStatus)
const isVerified =
(isVerifiedUser && verifiedStatus === 'valid') ||
(isVerifierUser && trustedVerifierStatus === 'valid')
return {
role: isVerifierUser ? 'verifier' : 'default',
isVerified,
}
}
-28
View File
@@ -1,28 +0,0 @@
import {AtUri} from '@atproto/api'
export function niceDate(date: number | string | Date) {
const d = new Date(date)
return `${d.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})} at ${d.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
})}`
}
export function getRkey({uri}: {uri: string}): string {
const at = new AtUri(uri)
return at.rkey
}
const formatter = new Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 1,
roundingMode: 'trunc',
})
export function prettyNumber(number: number) {
return formatter.format(number)
}
+1054
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -10,7 +10,7 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
@@ -19,5 +19,5 @@
"jsxFragmentFactory": "Fragment",
"downlevelIteration": true
},
"include": ["src", "snippet", "vite.config.ts"]
"include": ["src", "snippet", "vite.config.ts", "*.config.cjs", ".eslintrc.cjs"]
}
+32 -1
View File
@@ -1,10 +1,40 @@
import fs from 'node:fs'
import {resolve} from 'node:path'
import preact from '@preact/preset-vite'
import legacy from '@vitejs/plugin-legacy'
import type {UserConfig} from 'vite'
import type {Plugin, UserConfig} from 'vite'
import paths from 'vite-tsconfig-paths'
/**
* World's hackiest router, for dev only. Serves `/post.html` to requests that start with `/embed/`
*/
function devOnlyRouter(): Plugin {
return {
name: 'embed-to-post-html',
configureServer(server) {
server.middlewares.use((req, res, next) => {
const url = req.url || ''
if (!url.startsWith('/embed/')) return next()
const html = fs.readFileSync(
resolve(process.cwd(), 'post.html'),
'utf8',
)
server
.transformIndexHtml(url, html)
.then(transformed => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/html')
res.end(transformed)
})
.catch(next)
})
},
}
}
const config: UserConfig = {
plugins: [
preact(),
@@ -12,6 +42,7 @@ const config: UserConfig = {
legacy({
targets: ['defaults', 'not IE 11'],
}),
devOnlyRouter(),
],
build: {
assetsDir: 'static',
+1564 -1221
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
{
"Potentially Dangerous Link": "Potentially Dangerous Link",
"Blocked Link": "Blocked Link",
"This link may be malicious. You should proceed at your own risk.": "This link may be malicious. You should proceed at your own risk.",
"This link has been identified as malicious and has blocked for your safety.": "This link has been identified as malicious and has blocked for your safety.",
"Continue Anyway": "Continue Anyway",
"Return to Bluesky": "Return to Bluesky"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Potentially Dangerous Link": "Enlace Potencialmente Peligroso",
"Blocked Link": "Enlace Bloqueado",
"This link may be malicious. You should proceed at your own risk.": "Este enlace puede ser malicioso. Debes proceder bajo tu propio riesgo.",
"This link has been identified as malicious and has blocked for your safety.": "Este enlace ha sido identificado como malicioso y ha sido bloqueado por tu seguridad.",
"Continue Anyway": "Continuar de Todos Modos",
"Return to Bluesky": "Regresar a Bluesky"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Potentially Dangerous Link": "Lien potentiellement dangereux",
"Blocked Link": "Lien bloqué",
"This link may be malicious. You should proceed at your own risk.": "Ce lien est peut-être malveillant. Ne continuez qu’à vos risques et périls.",
"This link has been identified as malicious and has blocked for your safety.": "Ce lien a été identifié comme malveillant et a été bloqué pour votre sécurité.",
"Continue Anyway": "Continuer quand même",
"Return to Bluesky": "Retourner sur Bluesky"
}
+8 -1
View File
@@ -8,21 +8,28 @@
"build": "tsc"
},
"dependencies": {
"@atproto/common": "^0.4.0",
"@atproto/api": "^0.16.7",
"@atproto/common": "^0.4.11",
"@types/escape-html": "^1.0.4",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"escape-html": "^1.0.3",
"express": "^4.19.2",
"http-terminator": "^3.2.0",
"i18n": "^0.15.1",
"kysely": "^0.27.3",
"lru-cache": "^11.1.0",
"pg": "^8.12.0",
"pino": "^9.2.0",
"uint8arrays": "^5.1.0"
},
"devDependencies": {
"@types/body-parser": "^1.19.5",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/i18n": "^0.13.12",
"@types/pg": "^8.11.6",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
}
}
+55 -17
View File
@@ -1,24 +1,62 @@
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,
try {
httpLogger.info('Starting blink service')
const env = readEnv()
const cfg = envToCfg(env)
httpLogger.info(
{
port: cfg.service.port,
safelinkEnabled: cfg.service.safelinkEnabled,
hasDbUrl: !!cfg.db.url,
hasDbMigrationUrl: !!cfg.db.migrationUrl,
},
'Configuration loaded',
)
if (cfg.db.migrationUrl) {
httpLogger.info('Running database migrations')
const migrateDb = Database.postgres({
url: cfg.db.migrationUrl,
schema: cfg.db.schema,
})
await migrateDb.migrateToLatestOrThrow()
await migrateDb.close()
httpLogger.info('Database migrations completed')
}
httpLogger.info('Creating LinkService')
const link = await LinkService.create(cfg)
if (link.ctx.cfg.service.safelinkEnabled) {
httpLogger.info('Starting Safelink client')
link.ctx.safelinkClient.runFetchEvents()
}
await link.start()
httpLogger.info('Link service is running')
process.on('SIGTERM', async () => {
httpLogger.info('Link service is stopping')
await link.destroy()
httpLogger.info('Link service is stopped')
})
await migrateDb.migrateToLatestOrThrow()
await migrateDb.close()
} catch (error) {
httpLogger.error(
{
error: String(error),
stack: error instanceof Error ? error.stack : undefined,
},
'Failed to start blink service',
)
process.exit(1)
}
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()
main().catch(error => {
console.error('Unhandled startup error:', error)
process.exit(1)
})
+10
View File
@@ -0,0 +1,10 @@
import {type SafelinkRule} from '../db/schema'
export const exampleRule: SafelinkRule = {
id: 1,
eventType: 'addRule',
url: 'https://malicious.example.com/phishing',
pattern: 'domain',
action: 'block',
createdAt: '2024-06-01T12:00:00Z',
}
+352
View File
@@ -0,0 +1,352 @@
import {
AtpAgent,
CredentialSession,
type ToolsOzoneSafelinkDefs,
type ToolsOzoneSafelinkQueryEvents,
} from '@atproto/api'
import {ExpiredTokenError} from '@atproto/api/dist/client/types/com/atproto/server/confirmEmail.js'
import {MINUTE} from '@atproto/common'
import {LRUCache} from 'lru-cache'
import {type ServiceConfig} from '../config.js'
import type Database from '../db/index.js'
import {type SafelinkRule} from '../db/schema.js'
import {redirectLogger} from '../logger.js'
const SAFELINK_MIN_FETCH_INTERVAL = 1_000
const SAFELINK_MAX_FETCH_INTERVAL = 10_000
const SCHEME_REGEX = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//
export class SafelinkClient {
private domainCache: LRUCache<string, SafelinkRule | 'ok'>
private urlCache: LRUCache<string, SafelinkRule | 'ok'>
private db: Database
private ozoneAgent: OzoneAgent
private cursor?: string
constructor({cfg, db}: {cfg: ServiceConfig; db: Database}) {
this.domainCache = new LRUCache<string, SafelinkRule | 'ok'>({
max: 10000,
})
this.urlCache = new LRUCache<string, SafelinkRule | 'ok'>({
max: 25000,
})
this.db = db
this.ozoneAgent = new OzoneAgent(
cfg.safelinkPdsUrl!,
cfg.safelinkAgentIdentifier!,
cfg.safelinkAgentPass!,
)
}
public async tryFindRule(link: string): Promise<SafelinkRule | 'ok'> {
let url: string
let domain: string
try {
url = SafelinkClient.normalizeUrl(link)
domain = SafelinkClient.normalizeDomain(link)
} catch (e) {
redirectLogger.error(
{error: e, inputUrl: link},
'failed to normalize looked up link',
)
// fail open
return 'ok'
}
// First, check if there is an existing URL rule. Note that even if the rule is 'ok', we still
// want to check for a blocking domain rule, so we will only return here if the url rule exists
// _and_ it is not 'ok'.
const urlRule = this.urlCache.get(url)
if (urlRule && urlRule !== 'ok') {
return urlRule
}
// If we find a domain rule of _any_ kind, including 'ok', we can now return that rule.
const domainRule = this.domainCache.get(domain)
if (domainRule) {
return domainRule
}
try {
const maybeUrlRule = await this.getRule(this.db, url, 'url')
this.urlCache.set(url, maybeUrlRule)
return maybeUrlRule
} catch (e) {
this.urlCache.set(url, 'ok')
}
try {
const maybeDomainRule = await this.getRule(this.db, domain, 'domain')
this.domainCache.set(domain, maybeDomainRule)
return maybeDomainRule
} catch (e) {
this.domainCache.set(domain, 'ok')
}
return 'ok'
}
private async getRule(
db: Database,
url: string,
pattern: ToolsOzoneSafelinkDefs.PatternType,
): Promise<SafelinkRule> {
return db.db
.selectFrom('safelink_rule')
.selectAll()
.where('url', '=', url)
.where('pattern', '=', pattern)
.orderBy('createdAt', 'desc')
.executeTakeFirstOrThrow()
}
private async addRule(db: Database, rule: SafelinkRule) {
try {
if (rule.pattern === 'url') {
rule.url = SafelinkClient.normalizeUrl(rule.url)
} else if (rule.pattern === 'domain') {
rule.url = SafelinkClient.normalizeDomain(rule.url)
}
} catch (e) {
redirectLogger.error(
{error: e, inputUrl: rule.url},
'failed to normalize rule input URL',
)
return
}
db.db
.insertInto('safelink_rule')
.values({
id: rule.id,
eventType: rule.eventType,
url: rule.url,
pattern: rule.pattern,
action: rule.action,
createdAt: rule.createdAt,
})
.execute()
.catch(err => {
redirectLogger.error(
{error: err, rule},
'failed to add rule to database',
)
})
if (rule.pattern === 'domain') {
this.domainCache.delete(rule.url)
} else {
this.urlCache.delete(rule.url)
}
}
private async removeRule(db: Database, rule: SafelinkRule) {
try {
if (rule.pattern === 'url') {
rule.url = SafelinkClient.normalizeUrl(rule.url)
} else if (rule.pattern === 'domain') {
rule.url = SafelinkClient.normalizeDomain(rule.url)
}
} catch (e) {
redirectLogger.error(
{error: e, inputUrl: rule.url},
'failed to normalize rule input URL',
)
return
}
await db.db
.deleteFrom('safelink_rule')
.where('pattern', '=', 'domain')
.where('url', '=', rule.url)
.execute()
.catch(err => {
redirectLogger.error(
{error: err, rule},
'failed to remove rule from database',
)
})
if (rule.pattern === 'domain') {
this.domainCache.delete(rule.url)
} else {
this.urlCache.delete(rule.url)
}
}
public async runFetchEvents() {
let agent: AtpAgent
try {
agent = await this.ozoneAgent.getAgent()
} catch (err) {
redirectLogger.error({error: err}, 'error getting Ozone agent')
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
return
}
let res: ToolsOzoneSafelinkQueryEvents.Response
try {
const cursor = await this.getCursor()
res = await agent.tools.ozone.safelink.queryEvents({
cursor,
limit: 100,
sortDirection: 'asc',
})
} catch (err) {
if (err instanceof ExpiredTokenError) {
redirectLogger.info('ozone agent had expired session, refreshing...')
await this.ozoneAgent.refreshSession()
setTimeout(() => this.runFetchEvents(), SAFELINK_MIN_FETCH_INTERVAL)
return
}
redirectLogger.error(
{error: err},
'error fetching safelink events from Ozone',
)
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
return
}
if (res.data.events.length === 0) {
redirectLogger.info('received no new safelink events from ozone')
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
} else {
await this.db.transaction(async db => {
for (const rule of res.data.events) {
switch (rule.eventType) {
case 'removeRule':
await this.removeRule(db, rule)
break
case 'addRule':
case 'updateRule':
await this.addRule(db, rule)
break
default:
redirectLogger.warn({rule}, 'received unknown rule event type')
}
}
})
if (res.data.cursor) {
redirectLogger.info(
{cursor: res.data.cursor},
'received new safelink events from Ozone',
)
await this.setCursor(res.data.cursor)
}
setTimeout(() => this.runFetchEvents(), SAFELINK_MIN_FETCH_INTERVAL)
}
}
private async getCursor() {
if (this.cursor === '') {
const res = await this.db.db
.selectFrom('safelink_cursor')
.selectAll()
.where('id', '=', 1)
.executeTakeFirst()
if (!res) {
return ''
}
this.cursor = res.cursor
}
return this.cursor
}
private async setCursor(cursor: string) {
const updatedAt = new Date()
try {
await this.db.db
.insertInto('safelink_cursor')
.values({
id: 1,
cursor,
updatedAt,
})
.onConflict(oc => oc.column('id').doUpdateSet({cursor, updatedAt}))
.execute()
this.cursor = cursor
} catch (err) {
redirectLogger.error({error: err}, 'failed to update safelink cursor')
}
}
private static normalizeUrl(input: string) {
if (!SCHEME_REGEX.test(input)) {
input = `https://${input}`
}
const u = new URL(input)
u.hash = ''
let normalized = u.href.replace(SCHEME_REGEX, '').toLowerCase()
if (normalized.endsWith('/')) {
normalized = normalized.substring(0, normalized.length - 1)
}
return normalized
}
private static normalizeDomain(input: string) {
if (!SCHEME_REGEX.test(input)) {
input = `https://${input}`
}
const u = new URL(input)
return u.host.toLowerCase()
}
}
export class OzoneAgent {
private identifier: string
private password: string
private session: CredentialSession
private agent: AtpAgent
private refreshAt = 0
constructor(pdsHost: string, identifier: string, password: string) {
this.identifier = identifier
this.password = password
this.session = new CredentialSession(new URL(pdsHost))
this.agent = new AtpAgent(this.session)
}
public async getAgent() {
if (!this.identifier && !this.password) {
throw new Error(
'OZONE_AGENT_HANDLE and OZONE_AGENT_PASS environment variables must be set',
)
}
if (!this.session.hasSession) {
redirectLogger.info('creating Ozone session')
await this.session.login({
identifier: this.identifier,
password: this.password,
})
redirectLogger.info('ozone session created successfully')
this.refreshAt = Date.now() + 50 * MINUTE
}
if (Date.now() <= this.refreshAt) {
await this.refreshSession()
}
return this.agent
}
public async refreshSession() {
try {
await this.session.refreshSession()
this.refreshAt = Date.now() + 50 * MINUTE
} catch (e) {
redirectLogger.error({error: e}, 'error refreshing session')
}
}
}
+21 -2
View File
@@ -1,4 +1,4 @@
import {envInt, envList, envStr} from '@atproto/common'
import {envBool, envInt, envList, envStr} from '@atproto/common'
export type Config = {
service: ServiceConfig
@@ -9,7 +9,12 @@ export type ServiceConfig = {
port: number
version?: string
hostnames: string[]
hostnamesSet: Set<string>
appHostname: string
safelinkEnabled: boolean
safelinkPdsUrl?: string
safelinkAgentIdentifier?: string
safelinkAgentPass?: string
}
export type DbConfig = {
@@ -36,6 +41,10 @@ export type Environment = {
dbPostgresPoolSize?: number
dbPostgresPoolMaxUses?: number
dbPostgresPoolIdleTimeoutMs?: number
safelinkEnabled?: boolean
safelinkPdsUrl?: string
safelinkAgentIdentifier?: string
safelinkAgentPass?: string
}
export const readEnv = (): Environment => {
@@ -52,6 +61,10 @@ export const readEnv = (): Environment => {
dbPostgresPoolIdleTimeoutMs: envInt(
'LINK_DB_POSTGRES_POOL_IDLE_TIMEOUT_MS',
),
safelinkEnabled: envBool('LINK_SAFELINK_ENABLED'),
safelinkPdsUrl: envStr('LINK_SAFELINK_PDS_URL'),
safelinkAgentIdentifier: envStr('LINK_SAFELINK_AGENT_IDENTIFIER'),
safelinkAgentPass: envStr('LINK_SAFELINK_AGENT_PASS'),
}
}
@@ -60,7 +73,12 @@ export const envToCfg = (env: Environment): Config => {
port: env.port ?? 3000,
version: env.version,
hostnames: env.hostnames,
appHostname: env.appHostname || 'bsky.app',
hostnamesSet: new Set(env.hostnames),
appHostname: env.appHostname ?? 'bsky.app',
safelinkEnabled: env.safelinkEnabled ?? false,
safelinkPdsUrl: env.safelinkPdsUrl,
safelinkAgentIdentifier: env.safelinkAgentIdentifier,
safelinkAgentPass: env.safelinkAgentPass,
}
if (!env.dbPostgresUrl) {
throw new Error('Must configure postgres url (LINK_DB_POSTGRES_URL)')
@@ -75,6 +93,7 @@ export const envToCfg = (env: Environment): Config => {
size: env.dbPostgresPoolSize ?? 10,
},
}
return {
service: serviceCfg,
db: dbCfg,
+7 -1
View File
@@ -1,4 +1,5 @@
import {Config} from './config.js'
import {SafelinkClient} from './cache/safelinkClient.js'
import {type Config} from './config.js'
import Database from './db/index.js'
export type AppContextOptions = {
@@ -9,11 +10,16 @@ export type AppContextOptions = {
export class AppContext {
cfg: Config
db: Database
safelinkClient: SafelinkClient
abortController = new AbortController()
constructor(private opts: AppContextOptions) {
this.cfg = this.opts.cfg
this.db = this.opts.db
this.safelinkClient = new SafelinkClient({
cfg: this.opts.cfg.service,
db: this.opts.db,
})
}
static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
+10
View File
@@ -34,6 +34,16 @@ export class Database {
static postgres(opts: PgOptions): Database {
const {schema, url, txLockNonce} = opts
log.info(
{
schema,
poolSize: opts.poolSize,
poolMaxUses: opts.poolMaxUses,
poolIdleTimeoutMs: opts.poolIdleTimeoutMs,
},
'Creating database connection',
)
const pool =
opts.pool ??
new Pg.Pool({
@@ -0,0 +1,34 @@
import {type Kysely, sql} from 'kysely'
export async function up(db: Kysely<unknown>): Promise<void> {
await db.schema
.createTable('safelink_rule')
.addColumn('id', 'bigserial', col => col.primaryKey())
.addColumn('eventType', 'varchar', col => col.notNull())
.addColumn('url', 'varchar', col => col.notNull())
.addColumn('pattern', 'varchar', col => col.notNull())
.addColumn('action', 'varchar', col => col.notNull())
.addColumn('createdAt', 'timestamptz', col => col.notNull())
.execute()
await db.schema
.createTable('safelink_cursor')
.addColumn('id', 'bigserial', col => col.notNull())
.addColumn('cursor', 'varchar', col => col.notNull())
.addColumn('updatedAt', 'timestamptz', col => col.notNull())
.execute()
await db.schema
.createIndex('safelink_rule_url_pattern_created_at_idx')
.on('safelink_rule')
.expression(sql`"url", "pattern", "createdAt" DESC`)
.execute()
}
export async function down(db: Kysely<unknown>): Promise<void> {
await db.schema
.dropIndex('safelink_rule_url_pattern_created_at_idx')
.execute()
await db.schema.dropTable('safelink_rule').execute()
await db.schema.dropTable('safelink_cursor').execute()
}
@@ -0,0 +1,31 @@
import {type Kysely} from 'kysely'
export async function up(
db: Kysely<{safelink_rule: {}; safelink_cursor: {}}>,
): Promise<void> {
// Remove existing items from safelink_rule that were duplicated due to broken cursor
await db.deleteFrom(['safelink_rule']).execute()
// Delete the old cursor
await db.deleteFrom(['safelink_cursor']).execute()
await db.schema
.alterTable('safelink_cursor')
.addPrimaryKeyConstraint('pk_id', ['id'])
.execute()
}
export async function down(
db: Kysely<{safelink_rule: {}; safelink_cursor: {}}>,
): Promise<void> {
// Remove any rules that were added
await db.deleteFrom(['safelink_rule']).execute()
// Delete the cursor
await db.deleteFrom(['safelink_cursor']).execute()
await db.schema
.alterTable('safelink_cursor')
.dropConstraint('pk_id')
.execute()
}
+4
View File
@@ -1,5 +1,9 @@
import * as init from './001-init.js'
import * as safelink from './002-safelink.js'
import * as safelinkCursorConstraint from './003-safelink-cursor-constraint.js'
export default {
'001': init,
'002': safelink,
'003': safelinkCursorConstraint,
}
+21 -1
View File
@@ -1,7 +1,10 @@
import {Selectable} from 'kysely'
import {type ToolsOzoneSafelinkDefs} from '@atproto/api'
import {type Selectable} from 'kysely'
export type DbSchema = {
link: Link
safelink_rule: SafelinkRule
safelink_cursor: SafelinkCursor
}
export interface Link {
@@ -14,4 +17,21 @@ export enum LinkType {
StarterPack = 1,
}
export interface SafelinkRule {
id: number
eventType: ToolsOzoneSafelinkDefs.EventType
url: string
pattern: ToolsOzoneSafelinkDefs.PatternType
action: ToolsOzoneSafelinkDefs.ActionType
createdAt: string
}
export interface SafelinkCursor {
id: number
cursor: string
updatedAt: Date
}
export type LinkEntry = Selectable<Link>
export type SafelinkRuleEntry = Selectable<SafelinkRule>
export type SafelinkCursorEntry = Selectable<SafelinkCursor>
+21
View File
@@ -0,0 +1,21 @@
import escapeHTML from 'escape-html'
export function linkRedirectContents(link: string): string {
return `
<html>
<head>
<meta http-equiv="refresh" content="0; URL='${escapeHTML(link)}'" />
<meta
http-equiv="Cache-Control"
content="no-store, no-cache, must-revalidate, max-age=0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<style>
:root {
color-scheme: light dark;
}
</style>
</head>
</html>
`
}
+44
View File
@@ -0,0 +1,44 @@
import escapeHTML from 'escape-html'
import {type Request} from 'express'
export function linkWarningContents(
req: Request,
opts: {
type: 'warn' | 'block'
link: string
},
): string {
const continueButton =
opts.type === 'warn'
? `<a class="button secondary" href="${escapeHTML(opts.link)}">${req.__('Continue Anyway')}</a>`
: ''
return `
<div class="warning-icon">⚠️</div>
<h1>
${
opts.type === 'warn'
? req.__('Potentially Dangerous Link')
: req.__('Blocked Link')
}
</h1>
<p class="warning-text">
${
opts.type === 'warn'
? req.__(
'This link may be malicious. You should proceed at your own risk.',
)
: req.__(
'This link has been identified as malicious and has blocked for your safety.',
)
}
</p>
<div class="blocked-site">
<p class="site-url">${escapeHTML(opts.link)}</p>
</div>
<div class="button-group">
${continueButton}
<a class="button primary" href="https://bsky.app">${req.__('Return to Bluesky')}</a>
</div>
`
}
+120
View File
@@ -0,0 +1,120 @@
import escapeHTML from 'escape-html'
export function linkWarningLayout(
title: string,
containerContents: string,
): string {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Cache-Control"
content="no-store, no-cache, must-revalidate, max-age=0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${escapeHTML(title)}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial,
sans-serif;
background-color: #ffffff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 400px;
text-align: center;
}
.warning-icon {
font-size: 48px;
margin-bottom: 16px;
}
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
color: #000000;
}
.warning-text {
font-size: 15px;
color: #536471;
line-height: 1.4;
margin-bottom: 24px;
padding: 0 20px;
}
.blocked-site {
background-color: #f7f9fa;
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
text-align: left;
word-break: break-all;
}
.site-name {
font-size: 16px;
font-weight: 500;
color: #000000;
margin-bottom: 4px;
word-break: break-word;
display: block;
text-align: center;
}
.site-url {
font-size: 14px;
color: #536471;
word-break: break-all;
display: block;
text-align: center;
}
.button {
border: none;
border-radius: 24px;
padding: 12px 32px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
max-width: 280px;
transition: background-color 0.2s;
}
.primary {
background-color: #1d9bf0;
color: white;
}
.secondary {
}
.back-button:hover {
background-color: #1a8cd8;
}
.back-button:active {
background-color: #1681c4;
}
@media (max-width: 480px) {
.warning-text {
padding: 0 10px;
}
.blocked-site {
padding: 8px;
}
}
</style>
</head>
<body>
<div class="container">${containerContents}</div>
</body>
</html>
`
}
+15
View File
@@ -0,0 +1,15 @@
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import i18n from 'i18n'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
i18n.configure({
locales: ['en', 'es', 'fr'],
defaultLocale: 'en',
directory: path.join(__dirname, '../locales'),
})
export default i18n
+2
View File
@@ -7,6 +7,7 @@ import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
import {type Config} from './config.js'
import {AppContext} from './context.js'
import i18n from './i18n.js'
import {default as routes, errorHandler} from './routes/index.js'
export * from './config.js'
@@ -25,6 +26,7 @@ export class LinkService {
static async create(cfg: Config): Promise<LinkService> {
let app = express()
app.use(cors())
app.use(i18n.init)
const ctx = await AppContext.fromConfig(cfg)
app = routes(ctx, app)
+13 -2
View File
@@ -1,4 +1,15 @@
import {subsystemLogger} from '@atproto/common'
import {type Logger} from 'pino'
export const httpLogger = subsystemLogger('bskylink')
export const dbLogger = subsystemLogger('bskylink:db')
export const httpLogger: Logger = subsystemLogger('bskylink')
export const dbLogger: Logger = subsystemLogger('bskylink:db')
export const redirectLogger: Logger = subsystemLogger('bskylink:redirect')
redirectLogger.info = (
orig =>
(...args: any[]) => {
const [msg, ...rest] = args
orig.apply(redirectLogger, [String(msg), ...rest])
console.log('[bskylink:redirect]', ...args)
}
)(redirectLogger.info) as typeof redirectLogger.info
+8 -5
View File
@@ -1,9 +1,9 @@
import assert from 'node:assert'
import bodyParser from 'body-parser'
import {Express, Request} from 'express'
import {type Express, type Request} from 'express'
import {AppContext} from '../context.js'
import {type AppContext} from '../context.js'
import {LinkType} from '../db/schema.js'
import {randomId} from '../util.js'
import {handler} from './util.js'
@@ -83,18 +83,21 @@ const getUrl = (ctx: AppContext, req: Request, id: string) => {
: `https://${req.headers.host}`
return `${baseUrl}/${id}`
}
const baseUrl = ctx.cfg.service.hostnames.includes(req.headers.host)
? `https://${req.headers.host}`
const host = req.headers.host ?? ''
const baseUrl = ctx.cfg.service.hostnamesSet.has(host)
? `https://${host}`
: `https://${ctx.cfg.service.hostnames[0]}`
return `${baseUrl}/${id}`
}
const normalizedPathFromParts = (parts: string[]): string => {
// When given ['path1', 'path2', 'te:fg'], output should be
// /path1/path2/te:fg
return (
'/' +
parts
.map(encodeURIComponent)
.map(part => part.replaceAll('%3A', ':')) // preserve colons
.map(part => part.replace(/%3A/g, ':')) // preserve colons
.join('/')
)
}
+49 -6
View File
@@ -1,10 +1,13 @@
import assert from 'node:assert'
import {DAY, SECOND} from '@atproto/common'
import escapeHTML from 'escape-html'
import {type Express} from 'express'
import {type AppContext} from '../context.js'
import {linkRedirectContents} from '../html/linkRedirectContents.js'
import {linkWarningContents} from '../html/linkWarningContents.js'
import {linkWarningLayout} from '../html/linkWarningLayout.js'
import {redirectLogger} from '../logger.js'
import {handler} from './util.js'
const INTERNAL_IP_REGEX = new RegExp(
@@ -39,14 +42,54 @@ export default function (ctx: AppContext, app: Express) {
return res.status(302).end()
}
// Default to a max age header
res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`)
res.type('html')
res.status(200)
res.type('html')
const escaped = escapeHTML(url.href)
return res.send(
`<html><head><meta http-equiv="refresh" content="0; URL='${escaped}'" /><style>:root { color-scheme: light dark; }</style></head></html>`,
)
let html: string | undefined
if (ctx.cfg.service.safelinkEnabled) {
const rule = await ctx.safelinkClient.tryFindRule(link)
if (rule !== 'ok') {
switch (rule.action) {
case 'whitelist':
redirectLogger.info({rule}, 'Whitelist rule matched')
break
case 'block':
html = linkWarningLayout(
'Blocked Link Warning',
linkWarningContents(req, {
type: 'block',
link: url.href,
}),
)
res.setHeader('Cache-Control', 'no-store')
redirectLogger.info({rule}, 'Block rule matched')
break
case 'warn':
html = linkWarningLayout(
'Malicious Link Warning',
linkWarningContents(req, {
type: 'warn',
link: url.href,
}),
)
res.setHeader('Cache-Control', 'no-store')
redirectLogger.info({rule}, 'Warn rule matched')
break
default:
redirectLogger.warn({rule}, 'Unknown rule matched')
}
}
}
// If there is no html defined yet, we will create a redirect html
if (!html) {
html = linkRedirectContents(url.href)
}
return res.end(html)
}),
)
}
+239 -2
View File
@@ -1,7 +1,9 @@
import assert from 'node:assert'
import {AddressInfo} from 'node:net'
import {type AddressInfo} from 'node:net'
import {after, before, describe, it} from 'node:test'
import {ToolsOzoneSafelinkDefs} from '@atproto/api'
import {Database, envToCfg, LinkService, readEnv} from '../src/index.js'
describe('link service', async () => {
@@ -15,6 +17,10 @@ describe('link service', async () => {
appHostname: 'test.bsky.app',
dbPostgresSchema: 'link_test',
dbPostgresUrl: process.env.DB_POSTGRES_URL,
safelinkEnabled: true,
ozoneUrl: 'http://localhost:2583',
ozoneAgentHandle: 'mod-authority.test',
ozoneAgentPass: 'hunter2',
})
const migrateDb = Database.postgres({
url: cfg.db.url,
@@ -26,8 +32,85 @@ describe('link service', async () => {
await linkService.start()
const {port} = linkService.server?.address() as AddressInfo
baseUrl = `http://localhost:${port}`
})
// Ensure blocklist, whitelist, and safelink rules are set up
const now = new Date().toISOString()
linkService.ctx.cfg.eventCache.smartUpdate({
$type: 'tools.ozone.safelink.defs#event',
id: 1,
eventType: ToolsOzoneSafelinkDefs.ADDRULE,
url: 'https://en.wikipedia.org/wiki/Fight_Club',
pattern: ToolsOzoneSafelinkDefs.URL,
action: ToolsOzoneSafelinkDefs.WARN,
reason: ToolsOzoneSafelinkDefs.SPAM,
createdBy: 'did:example:admin',
createdAt: now,
comment: 'Do not talk about Fight Club',
})
linkService.ctx.cfg.eventCache.smartUpdate({
$type: 'tools.ozone.safelink.defs#event',
id: 2,
eventType: ToolsOzoneSafelinkDefs.ADDRULE,
url: 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a',
pattern: ToolsOzoneSafelinkDefs.URL,
action: ToolsOzoneSafelinkDefs.BLOCK,
reason: ToolsOzoneSafelinkDefs.SPAM,
createdBy: 'did:example:admin',
createdAt: now,
comment: 'All Bs',
})
linkService.ctx.cfg.eventCache.smartUpdate({
$type: 'tools.ozone.safelink.defs#event',
id: 3,
eventType: ToolsOzoneSafelinkDefs.ADDRULE,
url: 'https://en.wikipedia.org',
pattern: ToolsOzoneSafelinkDefs.DOMAIN,
action: ToolsOzoneSafelinkDefs.WHITELIST,
reason: ToolsOzoneSafelinkDefs.NONE,
createdBy: 'did:example:admin',
createdAt: now,
comment: 'Whitelisting the knowledge base of the internet',
})
linkService.ctx.cfg.eventCache.smartUpdate({
$type: 'tools.ozone.safelink.defs#event',
id: 4,
eventType: ToolsOzoneSafelinkDefs.ADDRULE,
url: 'https://www.instagram.com/teamseshbones/?hl=en',
pattern: ToolsOzoneSafelinkDefs.URL,
action: ToolsOzoneSafelinkDefs.BLOCK,
reason: ToolsOzoneSafelinkDefs.SPAM,
createdBy: 'did:example:admin',
createdAt: now,
comment: 'BONES has been erroneously blocked for the sake of this test',
})
const later = new Date(Date.now() + 1000).toISOString()
linkService.ctx.cfg.eventCache.smartUpdate({
$type: 'tools.ozone.safelink.defs#event',
id: 5,
eventType: ToolsOzoneSafelinkDefs.REMOVERULE,
url: 'https://www.instagram.com/teamseshbones/?hl=en',
pattern: ToolsOzoneSafelinkDefs.URL,
action: ToolsOzoneSafelinkDefs.REMOVERULE,
reason: ToolsOzoneSafelinkDefs.NONE,
createdBy: 'did:example:admin',
createdAt: later,
comment:
'BONES has been resurrected to bring good music to the world once again',
})
linkService.ctx.cfg.eventCache.smartUpdate({
$type: 'tools.ozone.safelink.defs#event',
id: 6,
eventType: ToolsOzoneSafelinkDefs.ADDRULE,
url: 'https://www.leagueoflegends.com/en-us/',
pattern: ToolsOzoneSafelinkDefs.URL,
action: ToolsOzoneSafelinkDefs.WARN,
reason: ToolsOzoneSafelinkDefs.SPAM,
createdBy: 'did:example:admin',
createdAt: now,
comment:
'Could be quite the mistake to get into this addicting game, but we will warn instead of block',
})
})
after(async () => {
await linkService?.destroy()
})
@@ -76,6 +159,80 @@ describe('link service', async () => {
assert.strictEqual(json.message, 'Link not found')
})
it('League of Legends warned', async () => {
const urlToRedirect = 'https://www.leagueoflegends.com/en-us/'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
// League of Legends is set to WARN, not BLOCK, so expect a warning (blocked-site div present)
assert.match(
html,
/Warning: Malicious Link/,
'Expected warning not found in HTML',
)
})
it('Wikipedia whitelisted, url restricted. Redirect safely since wikipedia is whitelisted', async () => {
const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(html, /meta http-equiv="refresh"/)
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
// Wikipedia domain is whitelisted, so no blocked-site div should be present
assert.doesNotMatch(html, /"blocked-site"/)
})
it('Unsafe redirect with block rule, due to the content of webpage.', async () => {
const urlToRedirect =
'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
assert.match(
html,
/"blocked-site"/,
'Expected blocked-site div not found in HTML',
)
})
it('Rule adjustment, safe redirect, 200 response for Instagram Account of teamsesh Bones', async () => {
// Retrieve the latest event after all updates
const result = linkService.ctx.cfg.eventCache.smartGet(
'https://www.instagram.com/teamseshbones/?hl=en',
)
assert(result, 'Expected event not found in eventCache')
assert.strictEqual(result.eventType, ToolsOzoneSafelinkDefs.REMOVERULE)
const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(html, /meta http-equiv="refresh"/)
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
})
async function getRedirect(link: string): Promise<[number, string]> {
const url = new URL(link)
const base = new URL(baseUrl)
@@ -121,3 +278,83 @@ describe('link service', async () => {
return payload.url
}
})
describe('link service no safelink', 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,
safelinkEnabled: false,
ozoneUrl: 'http://localhost:2583',
ozoneAgentHandle: 'mod-authority.test',
ozoneAgentPass: 'hunter2',
})
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('Wikipedia whitelisted, url restricted. Safelink is disabled, so redirect is always safe', async () => {
const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(html, /meta http-equiv="refresh"/)
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
// No blocked-site div, always safe
assert.doesNotMatch(html, /"blocked-site"/)
})
it('Unsafe redirect with block rule, but safelink is disabled so redirect is always safe', async () => {
const urlToRedirect =
'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(html, /meta http-equiv="refresh"/)
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
// No blocked-site div, always safe
assert.doesNotMatch(html, /"blocked-site"/)
})
it('Rule adjustment, safe redirect, safelink is disabled so always safe', async () => {
const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en'
const url = new URL(`${baseUrl}/redirect`)
url.searchParams.set('u', urlToRedirect)
const res = await fetch(url, {redirect: 'manual'})
assert.strictEqual(res.status, 200)
const html = await res.text()
assert.match(html, /meta http-equiv="refresh"/)
assert.match(
html,
new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
)
// No blocked-site div, always safe
assert.doesNotMatch(html, /"blocked-site"/)
})
})
+18 -9
View File
@@ -1,10 +1,19 @@
{
"compilerOptions": {
"module": "NodeNext",
"esModuleInterop": true,
"moduleResolution": "NodeNext",
"outDir": "dist",
"lib": ["ES2021.String"]
},
"include": ["./src/index.ts", "./src/bin.ts"]
}
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+333 -17
View File
@@ -2,27 +2,65 @@
# yarn lockfile v1
"@atproto/common-web@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.0.tgz#36da8c2c31d8cf8a140c3c8f03223319bf4430bb"
integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA==
"@atproto/api@^0.16.7":
version "0.16.7"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.16.7.tgz#eb0c520dbdaf74ba6f5ad7f9c6afe2d1389b8a0a"
integrity sha512-EdVWkEgaEQm1LEiiP1fW/XXXpMNmtvT5c9+cZVRiwYc4rTB66WIJJWqmaMT/tB7nccMkFjr6FtwObq5LewWfgw==
dependencies:
"@atproto/common-web" "^0.4.2"
"@atproto/lexicon" "^0.5.0"
"@atproto/syntax" "^0.4.1"
"@atproto/xrpc" "^0.7.4"
await-lock "^2.2.2"
multiformats "^9.9.0"
tlds "^1.234.0"
zod "^3.23.8"
"@atproto/common-web@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.2.tgz#6e3add6939da93d3dfbc8f87e26dc4f57fad7259"
integrity sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==
dependencies:
graphemer "^1.4.0"
multiformats "^9.9.0"
uint8arrays "3.0.0"
zod "^3.21.4"
zod "^3.23.8"
"@atproto/common@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.0.tgz#d77696c7eb545426df727837d9ee333b429fe7ef"
integrity sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g==
"@atproto/common@^0.4.11":
version "0.4.11"
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.11.tgz#9291b7c26f8b3507e280f7ecbdf1695ab5ea62f6"
integrity sha512-Knv0viYXNMfCdIE7jLUiWJKnnMfEwg+vz2epJQi8WOjqtqCFb3W/3Jn72ZiuovIfpdm13MaOiny6w2NErUQC6g==
dependencies:
"@atproto/common-web" "^0.3.0"
"@atproto/common-web" "^0.4.2"
"@ipld/dag-cbor" "^7.0.3"
cbor-x "^1.5.1"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
pino "^8.15.0"
pino "^8.21.0"
"@atproto/lexicon@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.5.0.tgz#4d2be425361f9ac7f9754b8a1ccba29ddf0b9460"
integrity sha512-3aAzEAy9EAPs3CxznzMhEcqDd7m3vz1eze/ya9/ThbB7yleqJIhz5GY2q76tCCwHPhn5qDDMhlA9kKV6fG23gA==
dependencies:
"@atproto/common-web" "^0.4.2"
"@atproto/syntax" "^0.4.1"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/syntax@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.4.1.tgz#f77bc610ae0914449ff3f4731861e3da429915f5"
integrity sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==
"@atproto/xrpc@^0.7.4":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.4.tgz#030342548797c1f344968c457a8659dbb60a2d60"
integrity sha512-sDi68+QE1XHegTaNAndlX41Gp827pouSzSs8CyAwhrqZdsJUxE3P7TMtrA0z+zAjvxVyvzscRc0TsN/fGUGrhw==
dependencies:
"@atproto/lexicon" "^0.5.0"
zod "^3.23.8"
"@cbor-extract/cbor-extract-darwin-arm64@2.2.0":
version "2.2.0"
@@ -54,6 +92,13 @@
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz#4b3f07af047f984c082de34b116e765cb9af975f"
integrity sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@ipld/dag-cbor@^7.0.3":
version "7.0.3"
resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e"
@@ -62,6 +107,95 @@
cborg "^1.6.0"
multiformats "^9.5.4"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@messageformat/core@^3.0.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.4.0.tgz#2814c23383dec7bddf535d54f2a03e410165ca9f"
integrity sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==
dependencies:
"@messageformat/date-skeleton" "^1.0.0"
"@messageformat/number-skeleton" "^1.0.0"
"@messageformat/parser" "^5.1.0"
"@messageformat/runtime" "^3.0.1"
make-plural "^7.0.0"
safe-identifier "^0.4.1"
"@messageformat/date-skeleton@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz#3bad068cbf5873d14592cfc7a73dd4d8615e2739"
integrity sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==
"@messageformat/number-skeleton@^1.0.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz#e7c245c41a1b2722bc59dad68f4d454f761bc9b4"
integrity sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==
"@messageformat/parser@^5.1.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.1.1.tgz#ca7d6c18e9f3f6b6bc984a465dac16da00106055"
integrity sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==
dependencies:
moo "^0.5.1"
"@messageformat/runtime@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@messageformat/runtime/-/runtime-3.0.1.tgz#94d1f6c43265c28ef7aed98ecfcc0968c6c849ac"
integrity sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==
dependencies:
make-plural "^7.0.0"
"@tsconfig/node10@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2"
integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==
"@tsconfig/node12@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
"@tsconfig/node14@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
"@tsconfig/node16@^1.0.2":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@types/body-parser@*", "@types/body-parser@^1.19.5":
version "1.19.6"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474"
integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==
dependencies:
"@types/connect" "*"
"@types/node" "*"
"@types/connect@*":
version "3.4.38"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
dependencies:
"@types/node" "*"
"@types/cors@^2.8.17":
version "2.8.17"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b"
@@ -74,6 +208,41 @@
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.4.tgz#dc7c166b76c7b03b27e32f80edf01d91eb5d9af2"
integrity sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==
"@types/express-serve-static-core@^4.17.33":
version "4.19.6"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267"
integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==
dependencies:
"@types/node" "*"
"@types/qs" "*"
"@types/range-parser" "*"
"@types/send" "*"
"@types/express@^4.17.21":
version "4.17.23"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef"
integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "^4.17.33"
"@types/qs" "*"
"@types/serve-static" "*"
"@types/http-errors@*":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472"
integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==
"@types/i18n@^0.13.12":
version "0.13.12"
resolved "https://registry.yarnpkg.com/@types/i18n/-/i18n-0.13.12.tgz#b457715766c63d8ffdfc51dd4fc1f72728e9d38f"
integrity sha512-iAd2QjKh+0ToBXocmCS3m38GskiaGzmSV1MTQz2GaOraqSqBiLf46J7u3EGINl+st+Uk4lO3OL7QyIjTJlrWIg==
"@types/mime@^1":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
"@types/node@*":
version "20.14.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18"
@@ -90,6 +259,33 @@
pg-protocol "*"
pg-types "^4.0.1"
"@types/qs@*":
version "6.14.0"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==
"@types/range-parser@*":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
"@types/send@*":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74"
integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==
dependencies:
"@types/mime" "^1"
"@types/node" "*"
"@types/serve-static@*":
version "1.15.8"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877"
integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==
dependencies:
"@types/http-errors" "*"
"@types/node" "*"
"@types/send" "*"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -105,6 +301,23 @@ accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
acorn-walk@^8.1.1:
version "8.3.4"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7"
integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
dependencies:
acorn "^8.11.0"
acorn@^8.11.0, acorn@^8.4.1:
version "8.15.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@@ -115,6 +328,11 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
await-lock@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -223,6 +441,11 @@ cors@^2.8.5:
object-assign "^4"
vary "^1"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -230,6 +453,13 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@^4.3.3:
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies:
ms "^2.1.3"
define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
@@ -259,6 +489,11 @@ detect-libc@^2.0.1:
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -446,6 +681,18 @@ http-terminator@^3.2.0:
roarr "^7.0.4"
type-fest "^2.3.3"
i18n@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/i18n/-/i18n-0.15.1.tgz#68fb8993c461cc440bc2485d82f72019f2b92de8"
integrity sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==
dependencies:
"@messageformat/core" "^3.0.0"
debug "^4.3.3"
fast-printf "^1.6.9"
make-plural "^7.0.0"
math-interval-parser "^2.0.1"
mustache "^4.2.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -478,6 +725,26 @@ kysely@^0.27.3:
resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276"
integrity sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==
lru-cache@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117"
integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
make-plural@^7.0.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.4.0.tgz#fa6990dd550dea4de6b20163f74e5ed83d8a8d6d"
integrity sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==
math-interval-parser@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/math-interval-parser/-/math-interval-parser-2.0.1.tgz#e22cd6d15a0a7f4c03aec560db76513da615bed4"
integrity sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -510,12 +777,17 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
moo@^0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.3:
ms@2.1.3, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -530,6 +802,11 @@ multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.9.0:
resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37"
integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==
mustache@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
@@ -690,7 +967,7 @@ pino-std-serializers@^7.0.0:
resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b"
integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==
pino@^8.15.0:
pino@^8.21.0:
version "8.21.0"
resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d"
integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==
@@ -848,6 +1125,11 @@ safe-buffer@5.2.1, safe-buffer@~5.2.0:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-identifier@^0.4.1:
version "0.4.2"
resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb"
integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==
safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
@@ -964,11 +1246,35 @@ thread-stream@^3.0.0:
dependencies:
real-require "^0.2.0"
tlds@^1.234.0:
version "1.260.0"
resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.260.0.tgz#666c2584cf627b4296e888b799ec341be42821ac"
integrity sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
ts-node@^10.9.2:
version "10.9.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
dependencies:
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
type-fest@^2.3.3:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
@@ -1016,6 +1322,11 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -1026,7 +1337,12 @@ xtend@^4.0.0:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
zod@^3.21.4:
version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
zod@^3.23.8:
version "3.25.76"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34"
integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
+3
View File
@@ -14,13 +14,16 @@
"@atproto/common": "^0.4.0",
"@resvg/resvg-js": "^2.6.2",
"express": "^4.19.2",
"express-prom-bundle": "^7.0.0",
"http-terminator": "^3.2.0",
"pino": "^9.2.0",
"prom-client": "^15.1.3",
"react": "^18.3.1",
"satori": "^0.10.13",
"twemoji": "^14.0.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.14.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
+4
View File
@@ -6,6 +6,7 @@ export type Config = {
export type ServiceConfig = {
port: number
metricsPort: number
version?: string
appviewUrl: string
originVerify?: string
@@ -13,6 +14,7 @@ export type ServiceConfig = {
export type Environment = {
port?: number
metricsPort?: number
version?: string
appviewUrl?: string
originVerify?: string
@@ -21,6 +23,7 @@ export type Environment = {
export const readEnv = (): Environment => {
return {
port: envInt('CARD_PORT'),
metricsPort: envInt('CARD_METRICS_PORT'),
version: envStr('CARD_VERSION'),
appviewUrl: envStr('CARD_APPVIEW_URL'),
originVerify: envStr('CARD_ORIGIN_VERIFY'),
@@ -30,6 +33,7 @@ export const readEnv = (): Environment => {
export const envToCfg = (env: Environment): Config => {
const serviceCfg: ServiceConfig = {
port: env.port ?? 3000,
metricsPort: env.metricsPort ?? 3001,
version: env.version,
appviewUrl: env.appviewUrl ?? 'https://api.bsky.app',
originVerify: env.originVerify,
+65 -2
View File
@@ -2,7 +2,9 @@ import events from 'node:events'
import type http from 'node:http'
import express from 'express'
import promBundle from 'express-prom-bundle'
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
import {register} from 'prom-client'
import {type Config} from './config.js'
import {AppContext} from './context.js'
@@ -13,7 +15,9 @@ export * from './logger.js'
export class CardService {
public server?: http.Server
public metricsServer?: http.Server
private terminator?: HttpTerminator
private metricsTerminator?: HttpTerminator
constructor(
public app: express.Application,
@@ -24,6 +28,30 @@ export class CardService {
let app = express()
const ctx = await AppContext.fromConfig(cfg)
// Add Prometheus middleware for automatic HTTP instrumentation
const metricsMiddleware = promBundle({
includeMethod: true,
includePath: true,
includeStatusCode: true,
includeUp: true,
promClient: {
collectDefaultMetrics: {},
},
autoregister: false,
normalizePath: req => {
// If we have a matched route, use its path (with :params) instead of the full URL path
if (req.route) {
return req.route.path
}
// Group all unmatched paths together to reduce cardinality
return '<unmatched>'
},
})
app.use(metricsMiddleware)
app = routes(ctx, app)
app.use(errorHandler)
@@ -31,14 +59,49 @@ export class CardService {
}
async start() {
// Start main application server
this.server = this.app.listen(this.ctx.cfg.service.port)
this.server.keepAliveTimeout = 90000
this.terminator = createHttpTerminator({server: this.server})
this.terminator = createHttpTerminator({
server: this.server,
gracefulTerminationTimeout: 15000, // 15s timeout for in-flight requests
})
await events.once(this.server, 'listening')
// Start separate metrics server
const metricsApp = express()
metricsApp.get('/metrics', async (_req, res) => {
res.set('Content-Type', register.contentType)
res.end(await register.metrics())
})
this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort)
this.metricsTerminator = createHttpTerminator({
server: this.metricsServer,
gracefulTerminationTimeout: 2000, // 2s timeout for metrics server
})
await events.once(this.metricsServer, 'listening')
}
async destroy() {
const startTime = Date.now()
this.ctx.abortController.abort()
await this.terminator?.terminate()
const shutdownPromises = []
if (this.terminator) {
shutdownPromises.push(this.terminator.terminate())
}
if (this.metricsTerminator) {
shutdownPromises.push(this.metricsTerminator.terminate())
}
await Promise.all(shutdownPromises)
const elapsed = Date.now() - startTime
const {httpLogger} = await import('./logger.js')
httpLogger.info(`Graceful shutdown completed in ${elapsed}ms`)
}
}
+371 -2
View File
@@ -123,6 +123,11 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@opentelemetry/api@^1.4.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
"@resvg/resvg-js-android-arm-eabi@2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz#e761e0b688127db64879f455178c92468a9aeabe"
@@ -229,6 +234,58 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@types/body-parser@*":
version "1.19.6"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474"
integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==
dependencies:
"@types/connect" "*"
"@types/node" "*"
"@types/connect@*":
version "3.4.38"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
dependencies:
"@types/node" "*"
"@types/express-serve-static-core@^4.17.33":
version "4.19.6"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267"
integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==
dependencies:
"@types/node" "*"
"@types/qs" "*"
"@types/range-parser" "*"
"@types/send" "*"
"@types/express@^4.17.21":
version "4.17.23"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef"
integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "^4.17.33"
"@types/qs" "*"
"@types/serve-static" "*"
"@types/http-errors@*":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472"
integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==
"@types/mime@^1":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
"@types/node@*":
version "24.5.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446"
integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==
dependencies:
undici-types "~7.12.0"
"@types/node@^20.14.3":
version "20.14.3"
resolved "https://registry.npmjs.org/@types/node/-/node-20.14.3.tgz"
@@ -236,6 +293,33 @@
dependencies:
undici-types "~5.26.4"
"@types/qs@*":
version "6.14.0"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==
"@types/range-parser@*":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
"@types/send@*":
version "0.17.5"
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74"
integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==
dependencies:
"@types/mime" "^1"
"@types/node" "*"
"@types/serve-static@*":
version "1.15.8"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877"
integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==
dependencies:
"@types/http-errors" "*"
"@types/node" "*"
"@types/send" "*"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz"
@@ -288,6 +372,11 @@ base64-js@^1.3.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bintrees@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8"
integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==
body-parser@1.20.2:
version "1.20.2"
resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz"
@@ -306,6 +395,24 @@ body-parser@1.20.2:
type-is "~1.6.18"
unpipe "1.0.0"
body-parser@1.20.3:
version "1.20.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.13.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
boolean@^3.1.4:
version "3.2.0"
resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz"
@@ -324,6 +431,14 @@ bytes@3.1.2:
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz"
@@ -335,6 +450,14 @@ call-bind@^1.0.7:
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
call-bound@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
dependencies:
call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0"
camelize@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz"
@@ -393,6 +516,11 @@ cookie@0.6.0:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
cookie@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@@ -463,6 +591,15 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
@@ -478,6 +615,11 @@ encodeurl@~1.0.2:
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
es-define-property@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz"
@@ -485,11 +627,23 @@ es-define-property@^1.0.0:
dependencies:
get-intrinsic "^1.2.4"
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
dependencies:
es-errors "^1.3.0"
escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
@@ -510,6 +664,53 @@ events@^3.3.0:
resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
express-prom-bundle@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/express-prom-bundle/-/express-prom-bundle-7.0.2.tgz#73a83b9033639dae81c78906a9df78844f3c93fa"
integrity sha512-ffFV4HGHvCKnkNJFqm42sYztRJE5mLgOj8MpGey1HOatuFhtcwXoBD2m5gca7ZbcyjkIf7lOH5ZdrhlrBf0sGw==
dependencies:
"@types/express" "^4.17.21"
express "^4.18.2"
on-finished "^2.3.0"
url-value-parser "^2.0.0"
express@^4.18.2:
version "4.21.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32"
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.7.1"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.3.1"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.12"
proxy-addr "~2.0.7"
qs "6.13.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.19.0"
serve-static "1.16.2"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
express@^4.19.2:
version "4.19.2"
resolved "https://registry.npmjs.org/express/-/express-4.19.2.tgz"
@@ -577,6 +778,19 @@ finalhandler@1.2.0:
statuses "2.0.1"
unpipe "~1.0.0"
finalhandler@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
dependencies:
debug "2.6.9"
encodeurl "~2.0.0"
escape-html "~1.0.3"
on-finished "2.4.1"
parseurl "~1.3.3"
statuses "2.0.1"
unpipe "~1.0.0"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
@@ -612,6 +826,30 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
has-symbols "^1.0.3"
hasown "^2.0.0"
get-intrinsic@^1.2.5, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz"
@@ -619,6 +857,11 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
@@ -646,7 +889,12 @@ has-symbols@^1.0.3:
resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
hasown@^2.0.0:
has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
hasown@^2.0.0, hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
@@ -747,6 +995,11 @@ make-error@^1.1.1:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
@@ -757,6 +1010,11 @@ merge-descriptors@1.0.1:
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-descriptors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
@@ -811,12 +1069,17 @@ object-inspect@^1.13.1:
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz"
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
object-inspect@^1.13.3:
version "1.13.4"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
on-exit-leak-free@^2.1.0:
version "2.1.2"
resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz"
integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==
on-finished@2.4.1:
on-finished@2.4.1, on-finished@^2.3.0:
version "2.4.1"
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
@@ -860,6 +1123,11 @@ parseurl@~1.3.3:
resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.12:
version "0.1.12"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
@@ -932,6 +1200,14 @@ process@^0.11.10:
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
prom-client@^15.1.3:
version "15.1.3"
resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2"
integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==
dependencies:
"@opentelemetry/api" "^1.4.0"
tdigest "^0.1.1"
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz"
@@ -947,6 +1223,13 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
qs@6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
quick-format-unescaped@^4.0.3:
version "4.0.4"
resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz"
@@ -1054,6 +1337,25 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
send@0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz"
@@ -1064,6 +1366,16 @@ serve-static@1.15.0:
parseurl "~1.3.3"
send "0.18.0"
serve-static@1.16.2:
version "1.16.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
dependencies:
encodeurl "~2.0.0"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.19.0"
set-function-length@^1.2.1:
version "1.2.2"
resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz"
@@ -1081,6 +1393,35 @@ setprototypeof@1.2.0:
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
side-channel-list@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
dependencies:
es-errors "^1.3.0"
object-inspect "^1.13.3"
side-channel-map@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
dependencies:
call-bound "^1.0.2"
es-errors "^1.3.0"
get-intrinsic "^1.2.5"
object-inspect "^1.13.3"
side-channel-weakmap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
dependencies:
call-bound "^1.0.2"
es-errors "^1.3.0"
get-intrinsic "^1.2.5"
object-inspect "^1.13.3"
side-channel-map "^1.0.1"
side-channel@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz"
@@ -1091,6 +1432,17 @@ side-channel@^1.0.4:
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
side-channel@^1.0.6:
version "1.1.0"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
dependencies:
es-errors "^1.3.0"
object-inspect "^1.13.3"
side-channel-list "^1.0.0"
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
sonic-boom@^3.7.0:
version "3.8.1"
resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz"
@@ -1127,6 +1479,13 @@ string_decoder@^1.3.0:
dependencies:
safe-buffer "~5.2.0"
tdigest@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced"
integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==
dependencies:
bintrees "1.0.2"
thread-stream@^2.6.0:
version "2.7.0"
resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz"
@@ -1220,6 +1579,11 @@ undici-types@~5.26.4:
resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~7.12.0:
version "7.12.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb"
integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==
unicode-trie@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz"
@@ -1238,6 +1602,11 @@ unpipe@1.0.0, unpipe@~1.0.0:
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
url-value-parser@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/url-value-parser/-/url-value-parser-2.2.0.tgz#f38ae8cd24604ec69bc219d66929ddbbd93a2b32"
integrity sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A==
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"

Some files were not shown because too many files have changed in this diff Show More