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:
+
+
{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"
+ ]
}