diff --git a/flake.nix b/flake.nix index 36e823134..9130076c2 100644 --- a/flake.nix +++ b/flake.nix @@ -85,7 +85,7 @@ just fastmod nodejs - yarn + pnpm crowdin-cli eas-cli diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb615bfe5..c10a51151 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,20 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - '@react-native/babel-preset': 0.81.5 - '@react-native/normalize-colors': 0.81.5 - '@expo/image-utils': 0.8.12 - multiformats: 9.9.0 - unicode-segmenter: 0.14.5 - '@types/estree': 1.0.6 - metro: 0.83.3 - metro-core: 0.83.3 - metro-config: 0.83.3 - metro-runtime: 0.83.3 - metro-source-map: 0.83.3 - react-native-mmkv: npm:@bsky.app/react-native-mmkv@2.12.5 - react-native-compressor: 1.13.0 - sonner-native: 0.21.0 + '@babel/helper-define-polyfill-provider>@babel/helper-compilation-targets': ^7.28.6 importers: @@ -2638,6 +2625,9 @@ packages: '@expo/html-elements@0.12.5': resolution: {integrity: sha512-28KWO88YKykKU7ke5sEQs5TivFRMs1Aktz13xxgqAf5rTgb+lka0VKVt3W2fG7ksbUQ407rtUqz7SEAq298NvQ==} + '@expo/image-utils@0.3.23': + resolution: {integrity: sha512-nhUVvW0TrRE4jtWzHQl8TR4ox7kcmrc2I0itaeJGjxF5A54uk7avgA0wRt7jP1rdvqQo1Ke1lXyLYREdhN9tPw==} + '@expo/image-utils@0.8.12': resolution: {integrity: sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==} @@ -2682,6 +2672,10 @@ packages: '@expo/sdk-runtime-versions@1.0.0': resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} + '@expo/spawn-async@1.5.0': + resolution: {integrity: sha512-LB7jWkqrHo+5fJHNrLAFdimuSXQ2MQ4lA7SQW5bf/HbsXuV2VrT/jN/M8f/KoWt0uJMGN4k/j7Opx4AvOOxSew==} + engines: {node: '>=4'} + '@expo/spawn-async@1.7.2': resolution: {integrity: sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==} engines: {node: '>=12'} @@ -4081,6 +4075,9 @@ packages: resolution: {integrity: sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w==} engines: {node: '>= 20.19.4'} + '@react-native/normalize-colors@0.74.89': + resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==} + '@react-native/normalize-colors@0.81.5': resolution: {integrity: sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==} @@ -5298,6 +5295,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@4.19.8': resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} @@ -6026,6 +6026,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -6679,10 +6683,18 @@ packages: cross-fetch@3.2.0: resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-random-string@1.0.0: + resolution: {integrity: sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==} + engines: {node: '>=4'} + css-background-parser@0.1.0: resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} @@ -7918,6 +7930,10 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fs-extra@9.0.0: + resolution: {integrity: sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==} + engines: {node: '>=10'} + fs-monkey@1.1.0: resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} @@ -9312,6 +9328,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mimic-fn@1.2.0: resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} engines: {node: '>=4'} @@ -9402,6 +9423,9 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + multiformats@13.4.2: + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} + multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} @@ -9450,6 +9474,9 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -9751,6 +9778,10 @@ packages: path-is-inside@1.0.2: resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -10516,6 +10547,12 @@ packages: react-native: '*' react-native-reanimated: '>=3.0.0' + react-native-mmkv@3.3.3: + resolution: {integrity: sha512-GMsfOmNzx0p5+CtrCFRVtpOOMYNJXuksBVARSQrCFaZwjUyHJdQzcN900GGaFFNTxw2fs8s5Xje//RDKj9+PZA==} + peerDependencies: + react: '*' + react-native: '*' + react-native-pager-view@6.8.0: resolution: {integrity: sha512-/wgFV8nB4TLnQ6j9e3TvNrHbPF5TINMZHaXt86GOV0NSJNMVGkWguniJVKrYLm85LL8KVhRkgdh43Rdu7PvW1A==} peerDependencies: @@ -10950,10 +10987,19 @@ packages: semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.3.2: + resolution: {integrity: sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==} + engines: {node: '>=10'} + hasBin: true + semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} @@ -11026,10 +11072,18 @@ packages: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -11431,6 +11485,14 @@ packages: tdigest@0.1.2: resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + temp-dir@1.0.0: + resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} + engines: {node: '>=4'} + + tempy@0.3.0: + resolution: {integrity: sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==} + engines: {node: '>=8'} + terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -11608,6 +11670,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@0.3.1: + resolution: {integrity: sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==} + engines: {node: '>=6'} + type-fest@0.7.1: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} @@ -11729,6 +11795,10 @@ packages: unicode-trie@2.0.0: resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unique-string@1.0.0: + resolution: {integrity: sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==} + engines: {node: '>=4'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -11737,6 +11807,10 @@ packages: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} + universalify@1.0.0: + resolution: {integrity: sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==} + engines: {node: '>= 10.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -12116,6 +12190,10 @@ packages: resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -12742,7 +12820,7 @@ snapshots: expo: 54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) expo-web-browser: 15.0.10(expo@54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0)) react-native: 0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0) - react-native-mmkv: '@bsky.app/react-native-mmkv@2.12.5(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)' + react-native-mmkv: 3.3.3(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) react-native-url-polyfill: 3.0.0(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0)) transitivePeerDependencies: - react @@ -15596,6 +15674,22 @@ snapshots: '@expo/html-elements@0.12.5': {} + '@expo/image-utils@0.3.23': + dependencies: + '@expo/spawn-async': 1.5.0 + chalk: 4.1.2 + fs-extra: 9.0.0 + getenv: 1.0.0 + jimp-compact: 0.16.1 + mime: 2.6.0 + node-fetch: 2.7.0 + parse-png: 2.1.0 + resolve-from: 5.0.0 + semver: 7.3.2 + tempy: 0.3.0 + transitivePeerDependencies: + - encoding + '@expo/image-utils@0.8.12': dependencies: '@expo/spawn-async': 1.7.2 @@ -15712,6 +15806,10 @@ snapshots: '@expo/sdk-runtime-versions@1.0.0': {} + '@expo/spawn-async@1.5.0': + dependencies: + cross-spawn: 6.0.6 + '@expo/spawn-async@1.7.2': dependencies: cross-spawn: 7.0.6 @@ -15758,6 +15856,7 @@ snapshots: - clean-css - csso - debug + - encoding - esbuild - supports-color - uglify-js @@ -16026,7 +16125,7 @@ snapshots: '@ipld/dag-cbor@9.2.6': dependencies: cborg: 5.1.0 - multiformats: 9.9.0 + multiformats: 13.4.2 '@isaacs/cliui@8.0.2': dependencies: @@ -17379,6 +17478,8 @@ snapshots: '@react-native/js-polyfills@0.81.5': {} + '@react-native/normalize-colors@0.74.89': {} + '@react-native/normalize-colors@0.81.5': {} '@react-native/typescript-config@0.81.6': {} @@ -18568,15 +18669,17 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.8': dependencies: '@types/node': 20.19.39 @@ -19405,6 +19508,8 @@ snapshots: asynckit@0.4.0: {} + at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} autoprefixer@10.5.0(postcss@8.5.10): @@ -20179,12 +20284,22 @@ snapshots: transitivePeerDependencies: - encoding + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + crypto-random-string@1.0.0: {} + css-background-parser@0.1.0: {} css-box-shadow@1.0.0-3: {} @@ -21312,11 +21427,13 @@ snapshots: expo-pwa@0.0.127(expo@54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)): dependencies: - '@expo/image-utils': 0.8.12 + '@expo/image-utils': 0.3.23 chalk: 4.1.2 commander: 2.20.0 expo: 54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) update-check: 1.5.3 + transitivePeerDependencies: + - encoding expo-screen-orientation@9.0.8(expo@54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0)): dependencies: @@ -21693,6 +21810,13 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-extra@9.0.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 1.0.0 + fs-monkey@1.1.0: {} fs.realpath@1.0.0: {} @@ -23443,6 +23567,8 @@ snapshots: mime@1.6.0: {} + mime@2.6.0: {} + mimic-fn@1.2.0: {} mimic-fn@2.1.0: {} @@ -23508,6 +23634,8 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 + multiformats@13.4.2: {} + multiformats@9.9.0: {} murmurhash@2.0.1: {} @@ -23538,6 +23666,8 @@ snapshots: nested-error-stacks@2.0.1: {} + nice-try@1.0.5: {} + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -23859,6 +23989,8 @@ snapshots: path-is-inside@1.0.2: {} + path-key@2.0.1: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -24687,6 +24819,11 @@ snapshots: react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) react-native-reanimated: 3.19.5(@babel/core@7.25.2)(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) + react-native-mmkv@3.3.3(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0) + react-native-pager-view@6.8.0(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 @@ -24772,7 +24909,7 @@ snapshots: react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.25.9 - '@react-native/normalize-colors': 0.81.5 + '@react-native/normalize-colors': 0.74.89 fbjs: 3.0.5 inline-style-prefixer: 7.0.1 memoize-one: 6.0.0 @@ -25121,7 +25258,7 @@ snapshots: rollup@4.60.2: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: '@rollup/rollup-android-arm-eabi': 4.60.2 '@rollup/rollup-android-arm64': 4.60.2 @@ -25250,8 +25387,12 @@ snapshots: semver-compare@1.0.0: {} + semver@5.7.2: {} + semver@6.3.1: {} + semver@7.3.2: {} + semver@7.5.4: dependencies: lru-cache: 6.0.0 @@ -25389,10 +25530,16 @@ snapshots: '@img/sharp-win32-ia32': 0.33.5 '@img/sharp-win32-x64': 0.33.5 + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 + shebang-regex@1.0.0: {} + shebang-regex@3.0.0: {} shell-quote@1.8.3: {} @@ -25858,6 +26005,14 @@ snapshots: dependencies: bintrees: 1.0.2 + temp-dir@1.0.0: {} + + tempy@0.3.0: + dependencies: + temp-dir: 1.0.0 + type-fest: 0.3.1 + unique-string: 1.0.0 + terminal-link@2.1.1: dependencies: ansi-escapes: 4.3.2 @@ -26060,6 +26215,8 @@ snapshots: type-fest@0.21.3: {} + type-fest@0.3.1: {} + type-fest@0.7.1: {} type-fest@1.4.0: {} @@ -26151,7 +26308,7 @@ snapshots: uint8arrays@5.1.1: dependencies: - multiformats: 9.9.0 + multiformats: 13.4.2 unbox-primitive@1.1.0: dependencies: @@ -26186,10 +26343,16 @@ snapshots: pako: 0.2.9 tiny-inflate: 1.0.3 + unique-string@1.0.0: + dependencies: + crypto-random-string: 1.0.0 + universalify@0.1.2: {} universalify@0.2.0: {} + universalify@1.0.0: {} + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -26515,7 +26678,7 @@ snapshots: webpack@5.106.2(@swc/core@1.15.30)(esbuild@0.27.7): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -26618,6 +26781,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 1bfc70f71..772e46953 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -1,5 +1,6 @@ import { createContext, + type PropsWithChildren, useCallback, useContext, useEffect, @@ -64,6 +65,9 @@ const ApiContext = createContext({ resumeSession: async () => {}, removeAccount: () => {}, partialRefreshSession: async () => {}, + createEphemeralAgent: async () => { + throw new Error('Not implemented') + }, }) ApiContext.displayName = 'SessionApiContext' @@ -108,7 +112,7 @@ class SessionStore { } } -export function Provider({children}: React.PropsWithChildren<{}>) { +export function Provider({children}: PropsWithChildren<{}>) { const ax = useAnalyticsBase() const cancelPendingTask = useOneTaskAtATime() // eslint-disable-next-line react/hook-use-state @@ -314,6 +318,33 @@ export function Provider({children}: React.PropsWithChildren<{}>) { }) }, [store, state, cancelPendingTask]) + const createEphemeralAgent = useCallback< + SessionApiContext['createEphemeralAgent'] + >( + async storedAccount => { + if (storedAccount.isOauthSession) { + const {agent} = await oauthResumeSession(storedAccount) + return agent + } + const {agent} = await createAgentAndResume( + storedAccount, + (ephemeralAgent, accountDid, sessionEvent) => { + const refreshedAccount = agentToSessionAccount(ephemeralAgent) + + store.dispatch({ + type: 'received-agent-event', + agent: ephemeralAgent as any, + refreshedAccount, + accountDid, + sessionEvent, + }) + }, + ) + return agent + }, + [store], + ) + const removeAccount = useCallback( account => { addSessionDebugLog({ @@ -388,6 +419,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { resumeSession, removeAccount, partialRefreshSession, + createEphemeralAgent, }), [ createAccount, @@ -397,6 +429,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { resumeSession, removeAccount, partialRefreshSession, + createEphemeralAgent, ], ) diff --git a/src/state/session/types.ts b/src/state/session/types.ts index 792724e25..ca5850f64 100644 --- a/src/state/session/types.ts +++ b/src/state/session/types.ts @@ -54,4 +54,7 @@ export type SessionApiContext = { * `persistSessionHandler`. */ partialRefreshSession: () => Promise + createEphemeralAgent: ( + account: SessionAccount, + ) => Promise } diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index d13a3b4bd..baa79e8d8 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1,6 +1,8 @@ import { Fragment, memo, + type ReactNode, + type RefObject, useCallback, useEffect, useImperativeHandle, @@ -106,10 +108,11 @@ import { useOpenRouterModel, } from '#/state/preferences/openrouter' import {usePreferencesQuery} from '#/state/queries/preferences' -import {useProfileQuery} from '#/state/queries/profile' +import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' import {type Gif} from '#/state/queries/tenor' -import {useAgent, useSession} from '#/state/session' +import {useAgent, useSession, useSessionApi} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {ComposerReplyTo} from '#/view/com/composer/ComposerReplyTo' @@ -134,6 +137,7 @@ import {VideoEmbedRedraft} from '#/view/com/composer/videos/VideoEmbedRedraft' import {VideoPreview} from '#/view/com/composer/videos/VideoPreview' import {VideoTranscodeProgress} from '#/view/com/composer/videos/VideoTranscodeProgress' import {UserAvatar} from '#/view/com/util/UserAvatar' +import {SwitchMenuItems} from '#/view/shell/desktop/LeftNav' import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -142,6 +146,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' +import * as Menu from '#/components/Menu' import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' @@ -205,15 +210,20 @@ export const ComposePost = ({ logContext, cancelRef, }: Props & { - cancelRef?: React.RefObject + cancelRef?: RefObject }) => { - const {currentAccount} = useSession() + const {currentAccount, accounts} = useSession() const t = useTheme() const ax = useAnalytics() const agent = useAgent() + const sessionApi = useSessionApi() const queryClient = useQueryClient() const currentDid = currentAccount!.did + + const [activeAccountDid, setActiveAccountDid] = useState(currentDid) + const {closeComposer} = useComposerControls() + const {requestSwitchToAccount} = useLoggedOutViewControls() const {t: l, i18n} = useLingui() const requireAltTextEnabled = useRequireAltTextEnabled() const omitViaField = useOmitViaField() @@ -345,7 +355,6 @@ export const ComposePost = ({ texts: splitPosts, }) }, [activePost.id, activePost.richtext.text, composerDispatch]) - const selectVideo = useCallback( (postId: string, asset: ImagePickerAsset) => { const abortController = new AbortController() @@ -902,13 +911,35 @@ export const ComposePost = ({ setError('') setIsPublishing(true) + let currentAgent = agent + let ephemeralAgent: BskyAgent | undefined let postUri: string | undefined let postSuccessData: OnPostSuccessData try { + if (activeAccountDid && activeAccountDid !== currentAccount?.did) { + const activeAccount = accounts.find(a => a.did === activeAccountDid) + if (activeAccount) { + try { + ephemeralAgent = await sessionApi.createEphemeralAgent(activeAccount) + currentAgent = ephemeralAgent + } catch (e) { + logger.error('Composer: failed to create ephemeral agent for account switch', { + message: e instanceof Error ? e.message : String(e), + }) + setIsPublishing(false) + requestSwitchToAccount({requestedAccount: activeAccount.did}) + Toast.show(l`Please sign in as @${activeAccount.handle} to post as them`, { + type: 'warning', + }) + return + } + } + } + logger.info(`composer: posting...`) postUri = ( await apilib.post( - agent, + currentAgent, queryClient, { thread: filteredThread, @@ -940,7 +971,7 @@ export const ComposePost = ({ 5, _e => true, async () => { - const res = await agent.app.bsky.unspecced.getPostThreadV2({ + const res = await currentAgent.app.bsky.unspecced.getPostThreadV2({ anchor: postUri!, above: false, below: filteredThread.posts.length - 1, @@ -988,6 +1019,11 @@ export const ComposePost = ({ setIsPublishing(false) return } finally { + if (ephemeralAgent && 'dispose' in ephemeralAgent) { + // @ts-ignore + ephemeralAgent.dispose() + } + if (postUri) { let index = 0 for (let post of filteredThread.posts) { @@ -1096,7 +1132,7 @@ export const ComposePost = ({ onPostSuccess, initQuote, replyTo, - setLangPrefs, + setPublishOnUpload, queryClient, navigation, composerState.draftId, @@ -1105,6 +1141,12 @@ export const ComposePost = ({ cleanupPublishedDraft, loadedDraftCreatedAt, emptyPostsPromptControl, + setLangPrefs, + accounts, + activeAccountDid, + currentAccount?.did, + sessionApi, + requestSwitchToAccount, ]) const handleConfirmSkipEmpty = () => { @@ -1305,8 +1347,10 @@ export const ComposePost = ({ canRemoveQuote={index > 0 || !initQuote} onSelectVideo={selectVideo} onClearVideo={clearVideo} - onPublish={onComposerPostPublish} onError={setError} + onPublish={onComposerPostPublish} + activeAccountDid={activeAccountDid} + setActiveAccountDid={setActiveAccountDid} /> {IS_WEBFooterSticky && post.id === activePost.id && ( {footer} @@ -1403,6 +1447,8 @@ let ComposerPost = memo(function ComposerPost({ onSelectVideo, onError, onPublish, + activeAccountDid, + setActiveAccountDid, }: { post: PostDraft dispatch: (action: ComposerAction) => void @@ -1418,11 +1464,12 @@ let ComposerPost = memo(function ComposerPost({ onSelectVideo: (postId: string, asset: ImagePickerAsset) => void onError: (error: string) => void onPublish: (richtext: RichText) => void + activeAccountDid: string + setActiveAccountDid: (did: string) => void }) { - const {currentAccount} = useSession() - const currentDid = currentAccount!.did + const {currentAccount, accounts} = useSession() const {t: l} = useLingui() - const {data: currentProfile} = useProfileQuery({did: currentDid}) + const {data: currentProfile} = useProfileQuery({did: activeAccountDid}) const richtext = post.richtext const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media const forceMinHeight = IS_WEB && isTextOnly && isActive @@ -1432,6 +1479,7 @@ let ComposerPost = memo(function ComposerPost({ : l`Add another post` : l`Anything but skeet` const discardPromptControl = Prompt.usePromptControl() + const signOutPromptControl = Prompt.usePromptControl() const enableSquareButtons = useEnableSquareButtons() @@ -1490,6 +1538,17 @@ let ComposerPost = memo(function ComposerPost({ [post.id, onSelectVideo, onImageAdd, l], ) + const {data} = useProfilesQuery({ + handles: accounts.map(acc => acc.did), + }) + const profiles = data?.profiles + + const allAccounts = accounts + .map(account => ({ + account, + profile: profiles?.find(p => p.did === account.did), + })) + useHideKeyboardOnBackground() return ( @@ -1502,12 +1561,37 @@ let ComposerPost = memo(function ComposerPost({ isTextOnly && isLastPost && IS_NATIVE && a.flex_grow, ]}> - + + + {({props}) => ( + + )} + + { + setActiveAccountDid(account.did)} + /> + } + - children?: React.ReactNode + children?: ReactNode }) { const t = useTheme() const {t: l} = useLingui() @@ -2738,7 +2822,7 @@ function ToolbarWrapper({ children, }: { style: StyleProp - children: React.ReactNode + children: ReactNode }) { if (IS_WEB) return children return ( diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 803a5baa1..066eee683 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,4 +1,4 @@ -import {type JSX, useCallback, useMemo, useState} from 'react' +import {type JSX, type MouseEvent, useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' import {type AppBskyActorDefs} from '@atproto/api' import {msg, plural} from '@lingui/core/macro' @@ -222,9 +222,11 @@ function ProfileCard() { ) } -function SwitchMenuItems({ +export function SwitchMenuItems({ accounts, signOutPromptControl, + showExtraButtons, + onSelectAccount, }: { accounts: | { @@ -233,11 +235,15 @@ function SwitchMenuItems({ }[] | undefined signOutPromptControl: DialogControlProps + showExtraButtons?: boolean + onSelectAccount?: (account: SessionAccount) => void }) { const {_} = useLingui() const {setShowLoggedOut} = useLoggedOutViewControls() const closeEverything = useCloseAllActiveElements() + showExtraButtons = showExtraButtons ?? true + const onAddAnotherAccount = () => { setShowLoggedOut(true) closeEverything() @@ -256,13 +262,14 @@ function SwitchMenuItems({ key={other.account.did} account={other.account} profile={other.profile} + onSelectAccount={onSelectAccount} /> ))} )} - + {showExtraButtons ? : undefined} @@ -271,12 +278,14 @@ function SwitchMenuItems({ Add another account - - - - Sign out - - + {showExtraButtons ? ( + + + + Sign out + + + ) : undefined} ) } @@ -301,7 +310,7 @@ function SwitcherMenuProfileLink() { currentAccount?.handle : isTab(currentRouteInfo.name, pathName) const onProfilePress = useCallback( - (e: React.MouseEvent) => { + (e: MouseEvent) => { if (e.ctrlKey || e.metaKey || e.altKey) { return } @@ -334,9 +343,11 @@ function SwitcherMenuProfileLink() { function SwitchMenuItem({ account, profile, + onSelectAccount, }: { account: SessionAccount profile: AppBskyActorDefs.ProfileViewDetailed | undefined + onSelectAccount?: (account: SessionAccount) => void }) { const {_} = useLingui() const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() @@ -353,7 +364,13 @@ function SwitchMenuItem({ '@', )}`, )} - onPress={() => void onPressSwitchAccount(account, 'SwitchAccount')}> + onPress={() => { + if (onSelectAccount) { + onSelectAccount(account) + } else { + void onPressSwitchAccount(account, 'SwitchAccount') + } + }}> () const onPressWrapped = useCallback( - (e: React.MouseEvent) => { + (e: MouseEvent) => { if (e.ctrlKey || e.metaKey || e.altKey) { return }