diff --git a/.github/workflows/server-publish.yml b/.github/workflows/server-publish.yml index b5aabbb8a2..1851cbcd03 100644 --- a/.github/workflows/server-publish.yml +++ b/.github/workflows/server-publish.yml @@ -33,7 +33,7 @@ jobs: registry: ghcr.io enableBuildKit: true multiPlatform: true - platform: linux/amd64,linux/arm64,linux/arm/v7 + platform: linux/amd64,linux/arm64 buildArgs: GIT_COMMIT=${{ inputs.commit }} tags: ${{ inputs.commit }}, latest username: ${{ github.actor }} diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json index 2a65c35aed..82b5334970 100644 --- a/auth/assets/custom-icons/_data/custom-icons.json +++ b/auth/assets/custom-icons/_data/custom-icons.json @@ -39,11 +39,15 @@ { "title": "Bloom Host", "slug": "bloom_host", - "altNames": ["Bloom Host Billing"] + "altNames": [ + "Bloom Host Billing" + ] }, { "title": "BorgBase", - "altNames": ["borg"], + "altNames": [ + "borg" + ], "slug": "BorgBase" }, { @@ -74,6 +78,12 @@ "title": "ConfigCat", "slug": "configcat" }, + { + "title": "CoinDCX" + }, + { + "title": "ConfigCat" + }, { "title": "Control D", "slug": "controld", @@ -84,7 +94,9 @@ }, { "title": "DCS", - "altNames": ["Digital Combat Simulator"], + "altNames": [ + "Digital Combat Simulator" + ], "slug": "dcs" }, { @@ -144,7 +156,9 @@ }, { "title": "Gosuslugi", - "altNames": ["Госуслуги"], + "altNames": [ + "Госуслуги" + ], "slug": "Gosuslugi" }, { @@ -220,7 +234,11 @@ { "title": "Local", "slug": "local_wp", - "altNames": ["LocalWP", "Local WP", "Local Wordpress"] + "altNames": [ + "LocalWP", + "Local WP", + "Local Wordpress" + ] }, { "title": "Marketplace.tf", @@ -228,14 +246,23 @@ }, { "title": "Mastodon", - "altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"], + "altNames": [ + "mstdn", + "fediscience", + "mathstodon", + "fosstodon" + ], "slug": "mastodon", "hex": "6364FF" }, { "title": "Mercado Livre", "slug": "mercado_livre", - "altNames": ["Mercado Libre", "MercadoLibre", "MercadoLivre"] + "altNames": [ + "Mercado Libre", + "MercadoLibre", + "MercadoLivre" + ] }, { "title": "Microsoft" @@ -251,7 +278,9 @@ }, { "title": "Murena", - "altNames": ["eCloud"], + "altNames": [ + "eCloud" + ], "slug": "ecloud" }, { @@ -342,7 +371,11 @@ { "title": "Registro br", "slug": "registro_br", - "altNames": ["Registro br", "registrobr", "Registro.br"] + "altNames": [ + "Registro br", + "registrobr", + "Registro.br" + ] }, { "title": "Render" @@ -408,7 +441,10 @@ }, { "title": "Techlore", - "altNames": ["Techlore Courses", "Techlore Forums"] + "altNames": [ + "Techlore Courses", + "Techlore Forums" + ] }, { "title": "Termius", @@ -440,7 +476,10 @@ }, { "title": "Twitch", - "altNames": ["Twitch.tv", "Twitch tv"] + "altNames": [ + "Twitch.tv", + "Twitch tv" + ] }, { "title": "Ubisoft", @@ -476,23 +515,32 @@ { "title": "WorkOS", "slug": "workos", - "altNames": ["Work OS"] + "altNames": [ + "Work OS" + ] }, { "title": "X", - "altNames": ["twitter"], + "altNames": [ + "twitter" + ], "slug": "x" }, { "title": "Yandex", - "altNames": ["Ya", "Яндекс"], + "altNames": [ + "Ya", + "Яндекс" + ], "slug": "Yandex" }, { "title": "YNAB", - "altNames": ["You Need A Budget"], + "altNames": [ + "You Need A Budget" + ], "slug": "ynab", "hex": "3B5EDA" } ] -} +} \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/coindcx.svg b/auth/assets/custom-icons/icons/coindcx.svg new file mode 100644 index 0000000000..a1c7bb7a1f --- /dev/null +++ b/auth/assets/custom-icons/icons/coindcx.svg @@ -0,0 +1,4 @@ + + + + diff --git a/auth/linux/packaging/appimage/make_config.yaml b/auth/linux/packaging/appimage/make_config.yaml index 9a3004dcd6..0b52ddf9e2 100644 --- a/auth/linux/packaging/appimage/make_config.yaml +++ b/auth/linux/packaging/appimage/make_config.yaml @@ -27,3 +27,6 @@ include: - libffi.so.8 - libtiff.so.5 - libjpeg.so.8 + +supported_mime_type: + - x-scheme-handler/enteauth \ No newline at end of file diff --git a/auth/linux/packaging/deb/make_config.yaml b/auth/linux/packaging/deb/make_config.yaml index 93f54e4720..755024d2a8 100644 --- a/auth/linux/packaging/deb/make_config.yaml +++ b/auth/linux/packaging/deb/make_config.yaml @@ -31,4 +31,4 @@ categories: startup_notify: false supported_mime_type: - - x-scheme-handler/ente + - x-scheme-handler/enteauth diff --git a/auth/linux/packaging/rpm/make_config.yaml b/auth/linux/packaging/rpm/make_config.yaml index e82dd63bfb..495f6482c6 100644 --- a/auth/linux/packaging/rpm/make_config.yaml +++ b/auth/linux/packaging/rpm/make_config.yaml @@ -28,4 +28,4 @@ categories: startup_notify: false supported_mime_type: - - x-scheme-handler/ente + - x-scheme-handler/enteauth diff --git a/auth/pubspec.lock b/auth/pubspec.lock index d49014e282..42abc06ba5 100644 --- a/auth/pubspec.lock +++ b/auth/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: archive - sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.5.0" + version: "3.6.1" args: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -133,18 +133,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.1" built_collection: dependency: transitive description: @@ -415,10 +415,10 @@ packages: dependency: "direct main" description: name: file_saver - sha256: bdebc720e17b3e01aba59da69b6d47020a7e5ba7d5c75bd9194f9618d5f16ef4 + sha256: d375b351e3331663abbaf99747abd72f159260c58fbbdbca9f926f02c01bdc48 url: "https://pub.dev" source: hosted - version: "0.2.12" + version: "0.2.13" fixnum: dependency: "direct main" description: @@ -444,10 +444,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a url: "https://pub.dev" source: hosted - version: "8.1.5" + version: "8.1.6" flutter_context_menu: dependency: "direct main" description: @@ -586,18 +586,18 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.20" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.2.2" flutter_secure_storage_linux: dependency: "direct overridden" description: @@ -611,34 +611,34 @@ packages: dependency: transitive description: name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.2" flutter_slidable: dependency: "direct main" description: @@ -685,10 +685,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66" + sha256: "7eae679e596a44fdf761853a706f74979f8dd3cd92cf4e23cae161fda091b847" url: "https://pub.dev" source: hosted - version: "8.2.5" + version: "8.2.6" freezed_annotation: dependency: transitive description: @@ -725,10 +725,10 @@ packages: dependency: "direct main" description: name: gradient_borders - sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + sha256: b1cd969552c83f458ff755aa68e13a0327d09f06c3f42f471b423b01427f21f8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" graphs: dependency: transitive description: @@ -757,10 +757,10 @@ packages: dependency: transitive description: name: hashlib_codecs - sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" + sha256: a1c7b5d89ff29e81fd8e8c0b35966db4c935e149fc4ebe1ebf71e358c15863ab url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.0" hex: dependency: transitive description: @@ -805,10 +805,10 @@ packages: dependency: transitive description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" intl: dependency: "direct main" description: @@ -893,18 +893,18 @@ packages: dependency: "direct main" description: name: local_auth_android - sha256: e0e5b1ea247c5a0951c13a7ee13dc1beae69750e6a2e1910d1ed6a3cd4d56943 + sha256: "48dfb2d954da8ef6a77adfc93a29998f7729e9308eaa817e91dea4500317b2c8" url: "https://pub.dev" source: hosted - version: "1.0.38" + version: "1.0.39" local_auth_darwin: dependency: "direct main" description: name: local_auth_darwin - sha256: "33381a15b0de2279523eca694089393bb146baebdce72a404555d03174ebc1e9" + sha256: e424ebf90d5233452be146d4a7da4bcd7a70278b67791592f3fde1bda8eef9e2 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.1" local_auth_platform_interface: dependency: transitive description: @@ -973,10 +973,10 @@ packages: dependency: "direct dev" description: name: mocktail - sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" modal_bottom_sheet: dependency: "direct main" description: @@ -1085,18 +1085,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.5" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -1141,10 +1141,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1157,10 +1157,10 @@ packages: dependency: "direct main" description: name: pointycastle - sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.9.1" pool: dependency: transitive description: @@ -1205,10 +1205,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" qr: dependency: transitive description: @@ -1245,18 +1245,18 @@ packages: dependency: "direct main" description: name: sentry - sha256: e572d33a3ff1d69549f33ee828a8ff514047d43ca8eea4ab093d72461205aa3e + sha256: "57514bc72d441ffdc463f498d6886aa586a2494fa467a1eb9d649c28010d7ee3" url: "https://pub.dev" source: hosted - version: "7.20.1" + version: "7.20.2" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: ac8cf6bb849f3560353ae33672e17b2713809a4e8de0d3cf372e9e9c42013757 + sha256: "9723d58470ca43a360681ddd26abb71ca7b815f706bc8d3747afd054cf639ded" url: "https://pub.dev" source: hosted - version: "7.20.1" + version: "7.20.2" share_plus: dependency: "direct main" description: @@ -1285,18 +1285,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -1341,10 +1341,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" shortid: dependency: transitive description: @@ -1370,10 +1370,10 @@ packages: dependency: transitive description: name: sodium_libs - sha256: f7f6719b7ab3e8512ce7a5ecd7bc8d865482431cdd5a07a46b55b13c152b54e1 + sha256: "441444f6f433032bae3444c2ef5ed2cf5bc0def77f104abdff20aedcf79a7c7a" url: "https://pub.dev" source: hosted - version: "2.2.1+1" + version: "2.2.1+5" source_gen: dependency: transitive description: @@ -1411,10 +1411,10 @@ packages: description: path: sqflite ref: HEAD - resolved-ref: f281785e12e8b1abf2f9d41a587fc83d810724cf + resolved-ref: "3309d399dd7d695bbfa7c05f643bb16765cef4ee" url: "https://github.com/tekartik/sqflite" source: git - version: "2.3.3" + version: "2.3.3+1" sqflite_common: dependency: transitive description: @@ -1435,18 +1435,18 @@ packages: dependency: "direct main" description: name: sqlite3 - sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202" + sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: fb2a106a2ea6042fe57de2c47074cc31539a941819c91e105b864744605da3f5 + sha256: "9f89a7e7dc36eac2035808427eba1c3fbd79e59c3a22093d8dace6d36b1fe89e" url: "https://pub.dev" source: hosted - version: "0.5.21" + version: "0.5.23" stack_trace: dependency: transitive description: @@ -1547,10 +1547,10 @@ packages: dependency: "direct main" description: name: tray_manager - sha256: e0ac9a88b2700f366b8629b97e8663b6ef450a2f169560a685dc167bfe9c9c29 + sha256: c9a63fd88bd3546287a7eb8ccc978d707eef82c775397af17dda3a4f4c039e64 url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.3" tuple: dependency: "direct main" description: @@ -1579,26 +1579,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" + sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.3" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.3.0" url_launcher_linux: dependency: transitive description: @@ -1611,10 +1611,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: @@ -1703,22 +1703,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078" + url: "https://pub.dev" + source: hosted + version: "0.1.5" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276 url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "3.0.0" win32: dependency: "direct main" description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.1" win32_registry: dependency: transitive description: @@ -1768,5 +1776,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/desktop/.github/workflows/desktop-release.yml b/desktop/.github/workflows/desktop-release.yml index 40fbbd3f85..e61bb6a7f0 100644 --- a/desktop/.github/workflows/desktop-release.yml +++ b/desktop/.github/workflows/desktop-release.yml @@ -17,9 +17,10 @@ on: # - cron: "45 2 * * 1-6" push: - # Run when a tag matching the pattern "v*"" is pushed. + # Run when a tag matching the pattern "vd.d.d"" is pushed. Crucially for + # us, this excludes the "-rc" tags. tags: - - "v*" + - "v[0-9]+.[0-9]+.[0-9]+" jobs: release: diff --git a/desktop/CHANGELOG.md b/desktop/CHANGELOG.md index 92220f8928..2dd484c0cf 100644 --- a/desktop/CHANGELOG.md +++ b/desktop/CHANGELOG.md @@ -1,6 +1,10 @@ # CHANGELOG -## v1.7.1 (Unreleased) +## v1.7.2 (Unreleased) + +- . + +## v1.7.1 - Support for passkeys as a second factor authentication mechanism. - Remember the window size across app restarts. diff --git a/desktop/docs/release.md b/desktop/docs/release.md index 7f5ebbe427..83d0120551 100644 --- a/desktop/docs/release.md +++ b/desktop/docs/release.md @@ -53,23 +53,24 @@ This'll trigger the workflow and create a new pre-release. We can edit this to add the release notes, convert it to a release. Once it is marked as latest, the release goes live. -We are done at this point, and can now create a new pre-release to host +We are done at this point, and can now update the other pre-release that hosts subsequent nightly builds. 1. Update `package.json` in the source repo to use version `1.x.x-rc`, and merge these changes into `main`. -2. In the release repo: +2. In the release repo, delete the existing _nightly_ pre-release, then: ```sh git tag 1.x.x-rc git push origin 1.x.x-rc ``` -3. Once the workflow finishes and the pre-release is created, edit its - description to "Nightly builds". +3. Start a new run of the workflow (`gh workflow run desktop-release.yml`). -4. Delete the pre-release for the previous (already released) version. +Once the workflow finishes and the 1.x.x-rc pre-release is created, edit its +description to "Nightly builds". Subsequent scheduled nightly builds will update +this pre-release. ## Workflow - Extra pre-releases diff --git a/desktop/package.json b/desktop/package.json index 80404e5c11..a84023f769 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "ente", - "version": "1.7.1-rc", + "version": "1.7.2-rc", "private": true, "description": "Desktop client for Ente Photos", "repository": "github:ente-io/photos-desktop", diff --git a/docs/docs/photos/features/passkeys.md b/docs/docs/photos/features/passkeys.md index f485c4f9b6..0d90f29870 100644 --- a/docs/docs/photos/features/passkeys.md +++ b/docs/docs/photos/features/passkeys.md @@ -5,11 +5,6 @@ description: Using passkeys as a second factor for your Ente account # Passkeys -> [!CAUTION] -> -> This is preview documentation for an upcoming feature. This feature has not -> yet been released yet, so the steps below will not work currently. - Passkeys are a new authentication mechanism that uses strong cryptography built into devices, like Windows Hello or Apple's Touch ID. **You can use passkeys as a second factor to secure your Ente account.** @@ -17,9 +12,13 @@ a second factor to secure your Ente account.** > [!TIP] > > Passkeys are the colloquial term for a WebAuthn (Web Authentication) -> credentials. To know more technical details about how our passkey verification -> works, you can see this -> [technical note in our source code](https://github.com/ente-io/ente/blob/main/web/docs/webauthn-passkeys.md). +> credentials. +> +> - More details about why and how are in the Passkeys announcement +> [blog post](https://ente.io/blog/introducing-passkeys-on-ente/). +> - And to know more technical details about how our passkey verification +> works, you can see this +> [technical note in our source code](https://github.com/ente-io/ente/blob/main/web/docs/webauthn-passkeys.md). ## Passkeys and TOTP diff --git a/docs/docs/photos/troubleshooting/desktop-install/index.md b/docs/docs/photos/troubleshooting/desktop-install/index.md index d5084be503..461446e29b 100644 --- a/docs/docs/photos/troubleshooting/desktop-install/index.md +++ b/docs/docs/photos/troubleshooting/desktop-install/index.md @@ -20,8 +20,12 @@ start it, then you might need to install the VC++ runtime from Microsoft. This is what the error looks like: +
+ ![Error when VC++ runtime is not installed](windows-vc.png){width=500px} +
+ You can install the Microsoft VC++ redistributable runtime from here:
https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version diff --git a/infra/workers/cast-albums/src/index.ts b/infra/workers/cast-albums/src/index.ts index 9f92fa9df6..6759126c17 100644 --- a/infra/workers/cast-albums/src/index.ts +++ b/infra/workers/cast-albums/src/index.ts @@ -1,4 +1,4 @@ -/** Proxy file and thumbnail requests for the cast web app */ +/** Proxy file and thumbnail requests for the cast web app. */ export default { async fetch(request: Request) { @@ -28,12 +28,12 @@ const handleOPTIONS = (request: Request) => { }; const isAllowedOrigin = (origin: string | null) => { + const allowed = ["cast.ente.io", "cast.ente.sh", "localhost"]; + if (!origin) return false; try { const url = new URL(origin); - return ["cast.ente.io", "cast.ente.sh", "localhost"].includes( - url.hostname - ); + return allowed.includes(url.hostname); } catch { // origin is likely an invalid URL return false; @@ -42,22 +42,27 @@ const isAllowedOrigin = (origin: string | null) => { const handleGET = async (request: Request) => { const url = new URL(request.url); - const castToken = - request.headers.get("X-Cast-Access-Token") ?? - url.searchParams.get("castToken"); + + const fileID = url.searchParams.get("fileID"); + if (!fileID) return new Response(null, { status: 400 }); + + let castToken = request.headers.get("X-Cast-Access-Token"); + if (!castToken) { + console.warn("Using deprecated castToken query param"); + castToken = url.searchParams.get("castToken"); + } + if (!castToken) { console.error("No cast token provided"); return new Response(null, { status: 400 }); } - const fileID = url.searchParams.get("fileID"); const pathname = url.pathname; - const params = new URLSearchParams({ castToken }); + let response = await fetch( `https://api.ente.io/cast/files${pathname}${fileID}?${params.toString()}` ); - response = new Response(response.body, response); response.headers.set("Access-Control-Allow-Origin", "*"); return response; diff --git a/infra/workers/files/package.json b/infra/workers/files/package.json new file mode 100644 index 0000000000..4ddcb5f853 --- /dev/null +++ b/infra/workers/files/package.json @@ -0,0 +1,10 @@ +{ + "name": "files", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/files/src/index.ts b/infra/workers/files/src/index.ts new file mode 100644 index 0000000000..7a5c982309 --- /dev/null +++ b/infra/workers/files/src/index.ts @@ -0,0 +1,101 @@ +/** Proxy requests for files. */ + +export default { + async fetch(request: Request) { + switch (request.method) { + case "OPTIONS": + return handleOPTIONS(request); + case "GET": + return handleGET(request); + default: + console.log(`Unsupported HTTP method ${request.method}`); + return new Response(null, { status: 405 }); + } + }, +} satisfies ExportedHandler; + +const handleOPTIONS = (request: Request) => { + const origin = request.headers.get("Origin"); + if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin); + const headers = request.headers.get("Access-Control-Request-Headers"); + if (!areAllowedHeaders(headers)) + console.warn("Unknown header in list", headers); + return new Response("", { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Max-Age": "86400", + // "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package", + "Access-Control-Allow-Headers": "*", + }, + }); +}; + +const isAllowedOrigin = (origin: string | null) => { + const desktopApp = "ente://app"; + const allowedHostnames = [ + "web.ente.io", + "photos.ente.io", + "photos.ente.sh", + "localhost", + ]; + + if (!origin) return false; + try { + const url = new URL(origin); + return origin == desktopApp || allowedHostnames.includes(url.hostname); + } catch { + // origin is likely an invalid URL + return false; + } +}; + +const areAllowedHeaders = (headers: string | null) => { + const allowed = ["x-auth-token", "x-client-package"]; + + if (!headers) return true; + for (const header of headers.split(",")) { + if (!allowed.includes(header.trim().toLowerCase())) return false; + } + return true; +}; + +const handleGET = async (request: Request) => { + const url = new URL(request.url); + + // Random bots keep trying to pentest causing noise in the logs. If the + // request doesn't have a fileID, we can just safely ignore it thereafter. + const fileID = url.searchParams.get("fileID"); + if (!fileID) return new Response(null, { status: 400 }); + + let token = request.headers.get("X-Auth-Token"); + if (!token) { + console.warn("Using deprecated token query param"); + token = url.searchParams.get("token"); + } + + if (!token) { + console.error("No token provided"); + // return new Response(null, { status: 400 }); + } + + // We forward the auth token as a query parameter to museum. This is so that + // it does not get preserved when museum does a redirect to the presigned S3 + // URL that serves the actual thumbnail. + // + // See: [Note: Passing credentials for self-hosted file fetches] + const params = new URLSearchParams(); + if (token) params.set("token", token); + + let response = await fetch( + `https://api.ente.io/files/download/${fileID}?${params.toString()}`, + { + headers: { + "User-Agent": request.headers.get("User-Agent") ?? "", + }, + } + ); + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + return response; +}; diff --git a/infra/workers/files/tsconfig.json b/infra/workers/files/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/files/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/files/wrangler.toml b/infra/workers/files/wrangler.toml new file mode 100644 index 0000000000..52349d8d03 --- /dev/null +++ b/infra/workers/files/wrangler.toml @@ -0,0 +1,11 @@ +name = "files" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +routes = [ + { pattern = "files.ente.io", custom_domain = true } +] + +tail_consumers = [ + { service = "tail" } +] diff --git a/infra/workers/health-check/package.json b/infra/workers/health-check/package.json new file mode 100644 index 0000000000..73802a826b --- /dev/null +++ b/infra/workers/health-check/package.json @@ -0,0 +1,10 @@ +{ + "name": "health-check", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/health-check/src/index.ts b/infra/workers/health-check/src/index.ts new file mode 100644 index 0000000000..afb0257534 --- /dev/null +++ b/infra/workers/health-check/src/index.ts @@ -0,0 +1,48 @@ +/** Ping api.ente.io every minute and yell if it doesn't pong. */ + +export default { + async scheduled(_, env: Env, ctx: ExecutionContext) { + ctx.waitUntil(ping(env, ctx)); + }, +} satisfies ExportedHandler; + +interface Env { + NOTIFY_URL: string; + CHAT_ID: string; +} + +const ping = async (env: Env, ctx: ExecutionContext) => { + const notify = async (msg: string) => + sendMessage(`${msg} on ${Date()}`, env); + + try { + let timeout = setTimeout(() => { + ctx.waitUntil(notify("Ping timed out")); + }, 5000); + const res = await fetch("https://api.ente.io/ping", { + headers: { + "User-Agent": "health-check", + }, + }); + clearTimeout(timeout); + if (!res.ok) await notify(`Ping failed (HTTP ${res.status})`); + } catch (e) { + await notify(`Ping failed (${e instanceof Error ? e.message : e})`); + } +}; + +const sendMessage = async (message: string, env: Env) => { + console.log(message); + const res = await fetch(env.NOTIFY_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + chat_id: parseInt(env.CHAT_ID), + parse_mode: "html", + text: message, + }), + }); + if (!res.ok) throw new Error(`Failed to sendMessage (HTTP ${res.status})`); +}; diff --git a/infra/workers/health-check/tsconfig.json b/infra/workers/health-check/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/health-check/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/health-check/wrangler.toml b/infra/workers/health-check/wrangler.toml new file mode 100644 index 0000000000..fadd49de5a --- /dev/null +++ b/infra/workers/health-check/wrangler.toml @@ -0,0 +1,17 @@ +name = "health-check" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +# Disable the default route, this worker does not handle fetch. +workers_dev = false + +tail_consumers = [{ service = "tail" }] + +[vars] +# Added as a secret via the Cloudflare dashboard +# NOTIFY_URL = "" +# CHAT_ID = "" + +[triggers] +# Every minute +crons = [ "*/1 * * * *" ] diff --git a/infra/workers/public-albums/package.json b/infra/workers/public-albums/package.json new file mode 100644 index 0000000000..946f42689f --- /dev/null +++ b/infra/workers/public-albums/package.json @@ -0,0 +1,10 @@ +{ + "name": "public-albums", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/public-albums/src/index.ts b/infra/workers/public-albums/src/index.ts new file mode 100644 index 0000000000..228d09fca3 --- /dev/null +++ b/infra/workers/public-albums/src/index.ts @@ -0,0 +1,98 @@ +/** Proxy requests for files and thumbnails in public albums. */ + +export default { + async fetch(request: Request) { + switch (request.method) { + case "OPTIONS": + return handleOPTIONS(request); + case "GET": + return handleGET(request); + default: + console.log(`Unsupported HTTP method ${request.method}`); + return new Response(null, { status: 405 }); + } + }, +} satisfies ExportedHandler; + +const handleOPTIONS = (request: Request) => { + const origin = request.headers.get("Origin"); + if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin); + const headers = request.headers.get("Access-Control-Request-Headers"); + if (!areAllowedHeaders(headers)) + console.warn("Unknown header in list", headers); + return new Response("", { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Max-Age": "86400", + // "Access-Control-Allow-Headers": "X-Auth-Access-Token, X-Auth-Access-Token-JWT", + // "Access-Control-Allow-Headers": "X-Auth-Access-Token, X-Auth-Access-Token-JWT, x-client-package", + "Access-Control-Allow-Headers": "*", + }, + }); +}; + +const isAllowedOrigin = (origin: string | null) => { + const allowed = ["albums.ente.io", "albums.ente.sh", "localhost"]; + + if (!origin) return false; + try { + const url = new URL(origin); + return allowed.includes(url.hostname); + } catch { + // origin is likely an invalid URL + return false; + } +}; + +const areAllowedHeaders = (headers: string | null) => { + // TODO(MR): Stop sending "x-client-package" + const allowed = [ + "x-auth-access-token", + "x-auth-access-token-jwt", + "x-client-package", + ]; + + if (!headers) return true; + for (const header of headers.split(",")) { + if (!allowed.includes(header.trim().toLowerCase())) return false; + } + return true; +}; + +const handleGET = async (request: Request) => { + const url = new URL(request.url); + + const fileID = url.searchParams.get("fileID"); + if (!fileID) return new Response(null, { status: 400 }); + + let accessToken = request.headers.get("X-Auth-Access-Token"); + if (accessToken === undefined) { + console.warn("Using deprecated accessToken query param"); + accessToken = url.searchParams.get("accessToken"); + } + + if (!accessToken) { + console.error("No accessToken provided"); + // return new Response(null, { status: 400 }); + } + + let accessTokenJWT = request.headers.get("X-Auth-Access-Token-JWT"); + if (accessTokenJWT === undefined) { + console.warn("Using deprecated accessTokenJWT query param"); + accessTokenJWT = url.searchParams.get("accessTokenJWT"); + } + + const pathname = url.pathname; + + const params = new URLSearchParams(); + if (accessToken) params.set("accessToken", accessToken); + if (accessTokenJWT) params.set("accessTokenJWT", accessTokenJWT); + + let response = await fetch( + `https://api.ente.io/public-collection/files${pathname}${fileID}?${params.toString()}` + ); + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + return response; +}; diff --git a/infra/workers/public-albums/tsconfig.json b/infra/workers/public-albums/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/public-albums/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/public-albums/wrangler.toml b/infra/workers/public-albums/wrangler.toml new file mode 100644 index 0000000000..9adad20f04 --- /dev/null +++ b/infra/workers/public-albums/wrangler.toml @@ -0,0 +1,11 @@ +name = "public-albums" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +routes = [ + { pattern = "public-albums.ente.io", custom_domain = true } +] + +tail_consumers = [ + { service = "tail" } +] diff --git a/infra/workers/tail/wrangler.toml b/infra/workers/tail/wrangler.toml index 5a4bce036e..1d6185c138 100644 --- a/infra/workers/tail/wrangler.toml +++ b/infra/workers/tail/wrangler.toml @@ -2,6 +2,9 @@ name = "tail" main = "src/index.ts" compatibility_date = "2024-06-14" +# Disable the default route, this worker does not handle fetch. +workers_dev = false + [vars] # Added as a secret via the Cloudflare dashboard # LOKI_PUSH_URL = "https://${loki_base_url}>/loki/api/v1/push" diff --git a/infra/workers/thumbnails/package.json b/infra/workers/thumbnails/package.json new file mode 100644 index 0000000000..e5107655bc --- /dev/null +++ b/infra/workers/thumbnails/package.json @@ -0,0 +1,10 @@ +{ + "name": "thumbnails", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/thumbnails/src/index.ts b/infra/workers/thumbnails/src/index.ts new file mode 100644 index 0000000000..997d0a7a0b --- /dev/null +++ b/infra/workers/thumbnails/src/index.ts @@ -0,0 +1,89 @@ +/** Proxy requests for thumbnails. */ + +export default { + async fetch(request: Request) { + switch (request.method) { + case "OPTIONS": + return handleOPTIONS(request); + case "GET": + return handleGET(request); + default: + console.log(`Unsupported HTTP method ${request.method}`); + return new Response(null, { status: 405 }); + } + }, +} satisfies ExportedHandler; + +const handleOPTIONS = (request: Request) => { + const origin = request.headers.get("Origin"); + if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin); + const headers = request.headers.get("Access-Control-Request-Headers"); + if (!areAllowedHeaders(headers)) + console.warn("Unknown header in list", headers); + return new Response("", { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Max-Age": "86400", + // "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package", + "Access-Control-Allow-Headers": "*", + }, + }); +}; + +const isAllowedOrigin = (origin: string | null) => { + const desktopApp = "ente://app"; + const allowedHostnames = [ + "web.ente.io", + "photos.ente.io", + "photos.ente.sh", + "localhost", + ]; + + if (!origin) return false; + try { + const url = new URL(origin); + return origin == desktopApp || allowedHostnames.includes(url.hostname); + } catch { + // origin is likely an invalid URL + return false; + } +}; + +const areAllowedHeaders = (headers: string | null) => { + const allowed = ["x-auth-token", "x-client-package"]; + + if (!headers) return true; + for (const header of headers.split(",")) { + if (!allowed.includes(header.trim().toLowerCase())) return false; + } + return true; +}; + +const handleGET = async (request: Request) => { + const url = new URL(request.url); + + const fileID = url.searchParams.get("fileID"); + if (!fileID) return new Response(null, { status: 400 }); + + let token = request.headers.get("X-Auth-Token"); + if (!token) { + console.warn("Using deprecated token query param"); + token = url.searchParams.get("token"); + } + + if (!token) { + console.error("No token provided"); + // return new Response(null, { status: 400 }); + } + + const params = new URLSearchParams(); + if (token) params.set("token", token); + + let response = await fetch( + `https://api.ente.io/files/preview/${fileID}?${params.toString()}` + ); + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + return response; +}; diff --git a/infra/workers/thumbnails/tsconfig.json b/infra/workers/thumbnails/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/thumbnails/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/thumbnails/wrangler.toml b/infra/workers/thumbnails/wrangler.toml new file mode 100644 index 0000000000..8f45b859e8 --- /dev/null +++ b/infra/workers/thumbnails/wrangler.toml @@ -0,0 +1,11 @@ +name = "thumbnails" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +routes = [ + { pattern = "thumbnails.ente.io", custom_domain = true } +] + +tail_consumers = [ + { service = "tail" } +] diff --git a/infra/workers/uploader/package.json b/infra/workers/uploader/package.json new file mode 100644 index 0000000000..e22b4eb1fc --- /dev/null +++ b/infra/workers/uploader/package.json @@ -0,0 +1,10 @@ +{ + "name": "uploader", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/uploader/src/index.ts b/infra/workers/uploader/src/index.ts new file mode 100644 index 0000000000..cd414815ba --- /dev/null +++ b/infra/workers/uploader/src/index.ts @@ -0,0 +1,124 @@ +/** + * Proxy file uploads. + * + * See: https://ente.io/blog/tech/making-uploads-faster/ + */ + +export default { + async fetch(request: Request) { + switch (request.method) { + case "OPTIONS": + return handleOPTIONS(request); + case "POST": + return handlePOSTOrPUT(request); + case "PUT": + return handlePOSTOrPUT(request); + default: + console.log(`Unsupported HTTP method ${request.method}`); + return new Response(null, { status: 405 }); + } + }, +} satisfies ExportedHandler; + +const handleOPTIONS = (request: Request) => { + const origin = request.headers.get("Origin"); + if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin); + const headers = request.headers.get("Access-Control-Request-Headers"); + if (!areAllowedHeaders(headers)) + console.warn("Unknown header in list", headers); + return new Response("", { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, PUT, OPTIONS", + "Access-Control-Max-Age": "86400", + // "Access-Control-Allow-Headers": "Content-Type", "UPLOAD-URL, X-Client-Package", + "Access-Control-Allow-Headers": "*", + "Access-Control-Expose-Headers": "X-Request-Id, CF-Ray", + }, + }); +}; + +const isAllowedOrigin = (origin: string | null) => { + const desktopApp = "ente://app"; + const allowedHostnames = [ + "web.ente.io", + "photos.ente.io", + "photos.ente.sh", + "localhost", + ]; + + if (!origin) return false; + try { + const url = new URL(origin); + return origin == desktopApp || allowedHostnames.includes(url.hostname); + } catch { + // origin is likely an invalid URL + return false; + } +}; + +const areAllowedHeaders = (headers: string | null) => { + const allowed = ["content-type", "upload-url", "x-client-package"]; + + if (!headers) return true; + for (const header of headers.split(",")) { + if (!allowed.includes(header.trim().toLowerCase())) return false; + } + return true; +}; + +const handlePOSTOrPUT = async (request: Request) => { + const url = new URL(request.url); + + const uploadURL = request.headers.get("UPLOAD-URL"); + if (!uploadURL) { + console.error("No uploadURL provided"); + return new Response(null, { status: 400 }); + } + + let response: Response; + switch (url.pathname) { + case "/file-upload": + response = await fetch(uploadURL, { + method: request.method, + body: request.body, + }); + break; + case "/multipart-upload": + response = await fetch(uploadURL, { + method: request.method, + body: request.body, + }); + if (response.ok) { + const etag = response.headers.get("etag"); + if (etag === null) { + console.log("No etag in response", response); + response = new Response(null, { status: 500 }); + } else { + response = new Response(JSON.stringify({ etag })); + } + } + break; + case "/multipart-complete": + response = await fetch(uploadURL, { + method: request.method, + body: request.body, + headers: { + "Content-Type": "text/xml", + }, + }); + break; + default: + return new Response(null, { status: 404 }); + } + + if (!response.ok) console.log("Request failed", response.status); + + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + response.headers.set( + "Access-Control-Expose-Headers", + "X-Request-Id, CF-Ray" + ); + return response; +}; diff --git a/infra/workers/uploader/tsconfig.json b/infra/workers/uploader/tsconfig.json new file mode 100644 index 0000000000..a65b752070 --- /dev/null +++ b/infra/workers/uploader/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "../tsconfig.base.json", "include": ["src"] } diff --git a/infra/workers/uploader/wrangler.toml b/infra/workers/uploader/wrangler.toml new file mode 100644 index 0000000000..9a03d8c6d5 --- /dev/null +++ b/infra/workers/uploader/wrangler.toml @@ -0,0 +1,7 @@ +name = "uploader" +main = "src/index.ts" +compatibility_date = "2024-06-14" + +routes = [{ pattern = "uploader.ente.io", custom_domain = true }] + +tail_consumers = [{ service = "tail" }] diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 37fcf3d4be..2a993972bd 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -38,6 +38,8 @@ export default function Index() { }, [publicKeyB64, privateKeyB64, pairingCode]); const pollTick = async () => { + if (!publicKeyB64 || !privateKeyB64 || !pairingCode) return; + const registration = { publicKeyB64, privateKeyB64, pairingCode }; try { const data = await getCastData(registration); diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 326b183d4a..7dd0f41832 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -9,9 +9,8 @@ import { isChromecast } from "services/chromecast"; import { imageURLGenerator } from "services/render"; export default function Slideshow() { - const [loading, setLoading] = useState(true); - const [imageURL, setImageURL] = useState(); const [isEmpty, setIsEmpty] = useState(false); + const [imageURL, setImageURL] = useState(); const router = useRouter(); @@ -35,7 +34,6 @@ export default function Slideshow() { } setImageURL(url); - setLoading(false); } } catch (e) { log.error("Failed to prepare generator", e); @@ -50,8 +48,8 @@ export default function Slideshow() { }; }, []); - if (loading) return ; if (isEmpty) return ; + if (!imageURL) return ; return isChromecast() ? ( diff --git a/web/apps/cast/src/services/cast-data.ts b/web/apps/cast/src/services/cast-data.ts index 587d1db323..b429eee72e 100644 --- a/web/apps/cast/src/services/cast-data.ts +++ b/web/apps/cast/src/services/cast-data.ts @@ -19,8 +19,8 @@ export const storeCastData = (payload: unknown) => { // Iterate through all the keys of the payload object and save them to // localStorage. We don't validate here, we'll validate when we read these // values back in `readCastData`. - for (const key in payload) { - window.localStorage.setItem(key, payload[key]); + for (const [key, value] of Object.entries(payload)) { + window.localStorage.setItem(key, value); } }; diff --git a/web/apps/cast/src/services/chromecast.ts b/web/apps/cast/src/services/chromecast.ts index e7539e8c51..77aa54aec1 100644 --- a/web/apps/cast/src/services/chromecast.ts +++ b/web/apps/cast/src/services/chromecast.ts @@ -141,6 +141,7 @@ const advertiseCode = (cast: Cast) => { const collectionID = data && typeof data == "object" && + "collectionID" in data && typeof data["collectionID"] == "string" ? data["collectionID"] : undefined; diff --git a/web/apps/cast/src/services/detect-type.ts b/web/apps/cast/src/services/detect-type.ts index c43529aaed..bbee910290 100644 --- a/web/apps/cast/src/services/detect-type.ts +++ b/web/apps/cast/src/services/detect-type.ts @@ -13,7 +13,7 @@ import FileType from "file-type"; * For the list of returned extensions, see (for our installed version): * https://github.com/sindresorhus/file-type/blob/main/core.d.ts */ -export const detectMediaMIMEType = async (file: File): Promise => { +export const detectMediaMIMEType = async (file: File) => { const chunkSizeForTypeDetection = 4100; const fileChunk = file.slice(0, chunkSizeForTypeDetection); const chunk = new Uint8Array(await fileChunk.arrayBuffer()); diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index 9edea9ca4f..bc50f19b59 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -81,7 +81,7 @@ export const register = async (): Promise => { await generateKeyPair(); // Register keypair with museum to get a pairing code. - let pairingCode: string; + let pairingCode: string | undefined; // eslint has fixed this spurious warning, but we're not on the latest // version yet, so add a disable. // https://github.com/eslint/eslint/pull/18286 diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 47e983ab1d..40b45e7e8e 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -7,6 +7,7 @@ import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import type { ComlinkWorker } from "@/next/worker/comlink-worker"; import { shuffled } from "@/utils/array"; +import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { ApiError } from "@ente/shared/error"; @@ -15,7 +16,7 @@ import { apiOrigin, customAPIOrigin } from "@ente/shared/network/api"; import type { AxiosResponse } from "axios"; import type { CastData } from "services/cast-data"; import { detectMediaMIMEType } from "services/detect-type"; -import { +import type { EncryptedEnteFile, EnteFile, FileMagicMetadata, @@ -133,7 +134,7 @@ export const imageURLGenerator = async function* (castData: CastData) { // The last to last element is the one that was shown prior to that, // and now can be safely revoked. if (previousURLs.length > 1) - URL.revokeObjectURL(previousURLs.shift()); + URL.revokeObjectURL(ensure(previousURLs.shift())); previousURLs.push(url); @@ -207,8 +208,8 @@ const decryptEnteFile = async ( metadata.decryptionHeader, fileKey, ); - let fileMagicMetadata: FileMagicMetadata; - let filePubMagicMetadata: FilePublicMagicMetadata; + let fileMagicMetadata: FileMagicMetadata | undefined; + let filePubMagicMetadata: FilePublicMagicMetadata | undefined; if (magicMetadata?.data) { fileMagicMetadata = { ...encryptedFile.magicMetadata, @@ -242,6 +243,8 @@ const decryptEnteFile = async ( if (file.pubMagicMetadata?.data.editedName) { file.metadata.title = file.pubMagicMetadata.data.editedName; } + // @ts-expect-error TODO: The core types need to be updated to allow the + // possibility of missing metadata fiels. return file; }; @@ -254,7 +257,7 @@ const isFileEligible = (file: EnteFile) => { // extension. To detect the actual type, we need to sniff the MIME type, but // that requires downloading and decrypting the file first. const [, extension] = nameAndExtension(file.metadata.title); - if (isNonWebImageFileExtension(extension)) { + if (extension && isNonWebImageFileExtension(extension)) { // Of the known non-web types, we support HEIC. return isHEICExtension(extension); } diff --git a/web/apps/cast/src/types/file/index.ts b/web/apps/cast/src/types/file/index.ts index c21f04a0ab..f294a54a18 100644 --- a/web/apps/cast/src/types/file/index.ts +++ b/web/apps/cast/src/types/file/index.ts @@ -1,5 +1,5 @@ import type { Metadata } from "@/media/types/file"; -import { +import type { EncryptedMagicMetadata, MagicMetadataCore, VISIBILITY_STATE, diff --git a/web/apps/cast/tsconfig.json b/web/apps/cast/tsconfig.json index cbdd32f742..bbe7217aad 100644 --- a/web/apps/cast/tsconfig.json +++ b/web/apps/cast/tsconfig.json @@ -1,25 +1,17 @@ { - "extends": "../../tsconfig.base.json", + "extends": "@/build-config/tsconfig-next.json", "compilerOptions": { + /* Set the base directory from which to resolve bare module names */ "baseUrl": "./src", - "downlevelIteration": true, - "jsx": "preserve", - "jsxImportSource": "@emotion/react", - "lib": ["dom", "dom.iterable", "esnext", "webworker"], - "noImplicitAny": false, - "noUnusedLocals": false, - "noUnusedParameters": false, - "strictNullChecks": false, - "target": "es5", - "useUnknownInCatchVariables": false + + /* TODO(MR): Enable this */ + "noUncheckedIndexedAccess": false, + /* MUI doesn't play great with exactOptionalPropertyTypes currently. */ + "exactOptionalPropertyTypes": false }, "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "**/*.js", - "../../packages/shared/themes/mui-theme.d.ts", - "../../packages/accounts/**/*.tsx" - ], - "exclude": ["node_modules", "out", ".next", "thirdparty"] + "src", + "../../packages/next/global-electron.d.ts", + "../../packages/shared/themes/mui-theme.d.ts" + ] }