From 6799ee38320dd8e2981edacad198e6a2e5529a62 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 16:15:09 +0530 Subject: [PATCH 01/16] Prune unused --- web/apps/photos/src/utils/useCastSender.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/web/apps/photos/src/utils/useCastSender.tsx b/web/apps/photos/src/utils/useCastSender.tsx index 0f3c6a316b..51615748a0 100644 --- a/web/apps/photos/src/utils/useCastSender.tsx +++ b/web/apps/photos/src/utils/useCastSender.tsx @@ -7,8 +7,6 @@ declare global { } } -import { useEffect, useState } from "react"; - type Sender = { chrome: typeof chrome; cast: typeof cast; @@ -43,20 +41,3 @@ export const loadSender = (() => { return promise; }; })(); - -export const useCastSender = () => { - const [sender, setSender] = useState( - { - chrome: null, - cast: null, - }, - ); - - useEffect(() => { - loadSender().then((sender) => { - setSender(sender); - }); - }, []); - - return sender; -}; From 28580cf107556e3c2127b9805c555af9b948c2dc Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 16:19:31 +0530 Subject: [PATCH 02/16] Prune --- .../Collections/AlbumCastDialog.tsx | 6 ++---- .../src/utils/{useCastSender.tsx => cast.ts} | 19 +++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) rename web/apps/photos/src/utils/{useCastSender.tsx => cast.ts} (75%) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 2683cc3ee0..da72f3b46a 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -14,7 +14,7 @@ import { t } from "i18next"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; import { v4 as uuidv4 } from "uuid"; -import { loadSender } from "../../utils/useCastSender"; +import { loadCast } from "../../utils/cast"; interface Props { show: boolean; @@ -100,9 +100,7 @@ export default function AlbumCastDialog({ useEffect(() => { if (view === "auto") { - loadSender().then(async (sender) => { - const { cast } = sender; - + loadCast().then(async (cast) => { const instance = await cast.framework.CastContext.getInstance(); try { await instance.requestSession(); diff --git a/web/apps/photos/src/utils/useCastSender.tsx b/web/apps/photos/src/utils/cast.ts similarity index 75% rename from web/apps/photos/src/utils/useCastSender.tsx rename to web/apps/photos/src/utils/cast.ts index 51615748a0..cd3cdabd05 100644 --- a/web/apps/photos/src/utils/useCastSender.tsx +++ b/web/apps/photos/src/utils/cast.ts @@ -7,16 +7,14 @@ declare global { } } -type Sender = { - chrome: typeof chrome; - cast: typeof cast; -}; - -export const loadSender = (() => { - let promise: Promise | null = null; +/** + * Load the Chromecast script, resolving with the global `cast` object. + */ +export const loadCast = (() => { + let promise: Promise | undefined; return () => { - if (promise === null) { + if (promise === undefined) { promise = new Promise((resolve) => { const script = document.createElement("script"); script.src = @@ -29,10 +27,7 @@ export const loadSender = (() => { chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, }); - resolve({ - chrome, - cast, - }); + resolve(cast); } }; document.body.appendChild(script); From c39a3c789d52a0cfe74877cdfc32e6dc1663b2ce Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 16:30:03 +0530 Subject: [PATCH 03/16] Add types --- .../Collections/AlbumCastDialog.tsx | 2 +- web/packages/new/package.json | 4 ++- .../new/photos/utils/chromecast-sender.ts | 33 +++++++++++++++++ web/yarn.lock | 36 +++++++++++++++++-- 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 web/packages/new/photos/utils/chromecast-sender.ts diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index da72f3b46a..ad8a2d4cdb 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -14,7 +14,7 @@ import { t } from "i18next"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; import { v4 as uuidv4 } from "uuid"; -import { loadCast } from "../../utils/cast"; +import { loadCast } from "@/new/photos/utils/chromecast-sender"; interface Props { show: boolean; diff --git a/web/packages/new/package.json b/web/packages/new/package.json index f870dd4a6f..83e10ba4e0 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -12,5 +12,7 @@ "idb": "^8.0.0", "zod": "^3.23.8" }, - "devDependencies": {} + "devDependencies": { + "@types/chromecast-caf-sender": "^1.0.10" + } } diff --git a/web/packages/new/photos/utils/chromecast-sender.ts b/web/packages/new/photos/utils/chromecast-sender.ts new file mode 100644 index 0000000000..70d82b9409 --- /dev/null +++ b/web/packages/new/photos/utils/chromecast-sender.ts @@ -0,0 +1,33 @@ +/// + +export type Cast = typeof cast; + +/** + * Load the Chromecast script, resolving with the global `cast` object. + */ +export const loadCast = (() => { + let promise: Promise | undefined; + + return () => { + if (promise === undefined) { + promise = new Promise((resolve) => { + const script = document.createElement("script"); + script.src = + "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; + window.__onGCastApiAvailable = (isAvailable) => { + if (isAvailable) { + cast.framework.CastContext.getInstance().setOptions({ + receiverApplicationId: "F5BCEC64", + autoJoinPolicy: + chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, + }); + + resolve(cast); + } + }; + document.body.appendChild(script); + }); + } + return promise; + }; +})(); diff --git a/web/yarn.lock b/web/yarn.lock index 2d6425cb67..1c768dfc9f 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -210,7 +210,7 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@11.13.1", "@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": +"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": version "11.13.1" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== @@ -238,7 +238,7 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@emotion/react@11.13.3", "@emotion/react@^11.13.3", "@emotion/react@^11.8.1": +"@emotion/react@11.13.3", "@emotion/react@^11.8.1": version "11.13.3" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4" integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== @@ -900,21 +900,53 @@ "@types/node" "*" base-x "^3.0.6" +"@types/chrome@*": + version "0.0.271" + resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.271.tgz#a7b2525291d35137d595b345f70ffde530b2d744" + integrity sha512-K0qgXvkwA5ic+/eygF1xiypHEvCoBgH5lwrhg3yva2mqJuCWyYm0vpZQ22GksAxgGfo0PWev9Zx3plp2clMlwg== + dependencies: + "@types/filesystem" "*" + "@types/har-format" "*" + "@types/chromecast-caf-receiver@^6.0.17": version "6.0.17" resolved "https://registry.yarnpkg.com/@types/chromecast-caf-receiver/-/chromecast-caf-receiver-6.0.17.tgz#c54bb6416ed07b78694db6e953be7ca966d09f06" integrity sha512-vIKyzx7mQglRVJuDl/WE5ehPdAvW4VDShe5I8ewi/8IQjTQkMc1Gx/LjkcpVfxfSSvZ1VKHPbPlJLScgCuAIeg== +"@types/chromecast-caf-sender@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/chromecast-caf-sender/-/chromecast-caf-sender-1.0.10.tgz#b1f9edb2c350e5d1de3dcb7374d28ddb73476166" + integrity sha512-B4iO+T4kMonmvIV+9xyWeIjxNWYVh6RyIQlFUeLk9fgQuXzHtFLnbnVwY7no5qshdUk9szKy0qbCWEMAjMkj4w== + dependencies: + "@types/chrome" "*" + "@types/estree@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/filesystem@*": + version "0.0.36" + resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" + integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA== + dependencies: + "@types/filewriter" "*" + +"@types/filewriter@*": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8" + integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g== + "@types/geojson@*": version "7946.0.14" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== +"@types/har-format@*": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.15.tgz#f352493638c2f89d706438a19a9eb300b493b506" + integrity sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA== + "@types/heic-convert@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/heic-convert/-/heic-convert-2.1.0.tgz#d19c7c655abb46321d7a0bc24387d47ac1d1661b" From 225dade722c8f430ea0bda1ad7d0fc18229a6968 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 16:36:08 +0530 Subject: [PATCH 04/16] Use types --- .../components/Collections/AlbumCastDialog.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index ad8a2d4cdb..93aef5f153 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -1,6 +1,7 @@ import { boxSeal } from "@/base/crypto/libsodium"; import log from "@/base/log"; import type { Collection } from "@/media/collection"; +import { loadCast } from "@/new/photos/utils/chromecast-sender"; import { VerticallyCentered } from "@ente/shared/components/Container"; import DialogBoxV2 from "@ente/shared/components/DialogBoxV2"; import EnteButton from "@ente/shared/components/EnteButton"; @@ -14,7 +15,6 @@ import { t } from "i18next"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; import { v4 as uuidv4 } from "uuid"; -import { loadCast } from "@/new/photos/utils/chromecast-sender"; interface Props { show: boolean; @@ -26,12 +26,6 @@ enum AlbumCastError { TV_NOT_FOUND = "tv_not_found", } -declare global { - interface Window { - chrome: any; - } -} - export default function AlbumCastDialog({ show, onHide, @@ -42,11 +36,12 @@ export default function AlbumCastDialog({ >("choose"); const [browserCanCast, setBrowserCanCast] = useState(false); - // Make API call on component mount + + // Make API call to clear all previous sessions on component mount. useEffect(() => { castGateway.revokeAllTokens(); - setBrowserCanCast(!!window.chrome); + setBrowserCanCast(typeof window["chrome"] !== "undefined"); }, []); const onSubmit: SingleInputFormProps["callback"] = async ( @@ -101,7 +96,7 @@ export default function AlbumCastDialog({ useEffect(() => { if (view === "auto") { loadCast().then(async (cast) => { - const instance = await cast.framework.CastContext.getInstance(); + const instance = cast.framework.CastContext.getInstance(); try { await instance.requestSession(); } catch (e) { From bb5261f73b019d4b8f326213c31503c2363a75f9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 16:42:46 +0530 Subject: [PATCH 05/16] Tweaks --- .../Collections/AlbumCastDialog.tsx | 38 ++++++------------- .../src/components/Collections/index.tsx | 2 +- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 93aef5f153..1e064eab14 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -16,7 +16,7 @@ import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; import { v4 as uuidv4 } from "uuid"; -interface Props { +interface AlbumCastDialogProps { show: boolean; onHide: () => void; currentCollection: Collection; @@ -26,11 +26,11 @@ enum AlbumCastError { TV_NOT_FOUND = "tv_not_found", } -export default function AlbumCastDialog({ +export const AlbumCastDialog: React.FC = ({ show, onHide, currentCollection, -}: Props) { +}) => { const [view, setView] = useState< "choose" | "auto" | "pin" | "auto-cast-error" >("choose"); @@ -73,6 +73,7 @@ export default function AlbumCastDialog({ if (!tvPublicKeyB64) { throw new Error(AlbumCastError.TV_NOT_FOUND); } + // generate random uuid string const castToken = uuidv4(); @@ -119,8 +120,8 @@ export default function AlbumCastDialog({ onHide(); }) .catch((e) => { - setView("auto-cast-error"); log.error("Error casting to TV", e); + setView("auto-cast-error"); }); } }, @@ -130,19 +131,14 @@ export default function AlbumCastDialog({ session .sendMessage("urn:x-cast:pair-request", { collectionID }) .then(() => { - log.debug(() => "Message sent successfully"); - }) - .catch((e) => { - log.error("Error sending message", e); + log.debug(() => "urn:x-cast:pair-request sent"); }); }); } }, [view]); useEffect(() => { - if (show) { - castGateway.revokeAllTokens(); - } + if (show) castGateway.revokeAllTokens(); }, [show]); return ( @@ -163,12 +159,8 @@ export default function AlbumCastDialog({ { - setView("auto"); - }} + style={{ marginBottom: "1rem" }} + onClick={() => setView("auto")} > {t("cast_auto_pair")} @@ -178,11 +170,7 @@ export default function AlbumCastDialog({ {t("pair_with_pin_description")} - { - setView("pin"); - }} - > + setView("pin")}> {t("pair_with_pin")} @@ -241,9 +229,7 @@ export default function AlbumCastDialog({ /> { - setView("choose"); - }} + onClick={() => setView("choose")} > {t("GO_BACK")} @@ -251,4 +237,4 @@ export default function AlbumCastDialog({ )} ); -} +}; diff --git a/web/apps/photos/src/components/Collections/index.tsx b/web/apps/photos/src/components/Collections/index.tsx index b81e9fa642..5dd7e9c23e 100644 --- a/web/apps/photos/src/components/Collections/index.tsx +++ b/web/apps/photos/src/components/Collections/index.tsx @@ -24,7 +24,7 @@ import { isFilesDownloadCancelled, isFilesDownloadCompleted, } from "../FilesDownloadProgress"; -import AlbumCastDialog from "./AlbumCastDialog"; +import { AlbumCastDialog } from "./AlbumCastDialog"; /** * Specifies what the bar is displaying currently. From f118a9d2f2d35d3b50d9c8f1f2ffdcc763b899fb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 16:43:27 +0530 Subject: [PATCH 06/16] Rename to mirror sender --- web/apps/cast/src/pages/index.tsx | 2 +- web/apps/cast/src/pages/slideshow.tsx | 2 +- .../src/services/{chromecast.ts => chromecast-receiver.ts} | 0 web/apps/cast/src/services/render.ts | 2 +- web/yarn.lock | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) rename web/apps/cast/src/services/{chromecast.ts => chromecast-receiver.ts} (100%) diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index ad031f863b..2a49e3d43f 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -6,7 +6,7 @@ import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; import { readCastData, storeCastData } from "services/cast-data"; import { getCastData, register } from "services/pair"; -import { advertiseOnChromecast } from "../services/chromecast"; +import { advertiseOnChromecast } from "../services/chromecast-receiver"; export default function Index() { const [publicKeyB64, setPublicKeyB64] = useState(); diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 7dba6c8fd5..1d206867c4 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -5,7 +5,7 @@ import { FilledCircleCheck } from "components/FilledCircleCheck"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; import { readCastData } from "services/cast-data"; -import { isChromecast } from "services/chromecast"; +import { isChromecast } from "services/chromecast-receiver"; import { imageURLGenerator } from "services/render"; export default function Slideshow() { diff --git a/web/apps/cast/src/services/chromecast.ts b/web/apps/cast/src/services/chromecast-receiver.ts similarity index 100% rename from web/apps/cast/src/services/chromecast.ts rename to web/apps/cast/src/services/chromecast-receiver.ts diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index b19e41bfe1..21c9925a03 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -28,7 +28,7 @@ import HTTPService from "@ente/shared/network/HTTPService"; import type { AxiosResponse } from "axios"; import type { CastData } from "services/cast-data"; import { detectMediaMIMEType } from "services/detect-type"; -import { isChromecast } from "./chromecast"; +import { isChromecast } from "./chromecast-receiver"; /** * An async generator function that loops through all the files in the diff --git a/web/yarn.lock b/web/yarn.lock index 1c768dfc9f..4c1941f89d 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -210,7 +210,7 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": +"@emotion/cache@11.13.1", "@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": version "11.13.1" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== @@ -238,7 +238,7 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@emotion/react@11.13.3", "@emotion/react@^11.8.1": +"@emotion/react@11.13.3", "@emotion/react@^11.13.3", "@emotion/react@^11.8.1": version "11.13.3" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4" integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== From 1895e90b3ea3457a4709476681140e27a6c1c5a8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:02:49 +0530 Subject: [PATCH 07/16] Workaround --- web/packages/new/package.json | 4 +- .../new/photos/utils/chromecast-sender.ts | 56 ++++++++++++++++++- web/yarn.lock | 32 ----------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/web/packages/new/package.json b/web/packages/new/package.json index 83e10ba4e0..f870dd4a6f 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -12,7 +12,5 @@ "idb": "^8.0.0", "zod": "^3.23.8" }, - "devDependencies": { - "@types/chromecast-caf-sender": "^1.0.10" - } + "devDependencies": {} } diff --git a/web/packages/new/photos/utils/chromecast-sender.ts b/web/packages/new/photos/utils/chromecast-sender.ts index 70d82b9409..8b5f4e93df 100644 --- a/web/packages/new/photos/utils/chromecast-sender.ts +++ b/web/packages/new/photos/utils/chromecast-sender.ts @@ -1,12 +1,62 @@ -/// +/* eslint-disable @typescript-eslint/no-namespace */ +/** + * The types for the sender are already available as + * "@types/chromecast-caf-sender", however installing them breaks the types for + * the cast receiver in apps/cast. Vice-versa, having those types for the + * receiver ("@types/chromecast-caf-receiver") conflicts with the types that we + * add for the sender. + * + * As a workaround, this file includes the handpicked interface from + * "@types/chromecast-caf-sender" for only the parts that we use. + */ -export type Cast = typeof cast; +declare global { + interface Window { + cast: typeof cast; + __onGCastApiAvailable(available: boolean, reason?: string): void; + } +} + +declare namespace chrome.cast { + /** + * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.AutoJoinPolicy + */ + export enum AutoJoinPolicy { + ORIGIN_SCOPED = "origin_scoped", + } +} + +/** + * Cast Application Framework + * @see https://developers.google.com/cast/docs/reference/chrome/cast.framework + */ +declare namespace cast.framework { + interface CastOptions { + autoJoinPolicy: chrome.cast.AutoJoinPolicy; + receiverApplicationId?: string | undefined; + } + + class CastContext { + static getInstance(): CastContext; + setOptions(options: CastOptions): void; + requestSession(): Promise; + getCurrentSession(): CastSession | null; + } + + class CastSession { + sendMessage(namespace: string, data: unknown): Promise; + addMessageListener( + namespace: string, + listener: (namespace: string, message: string) => void, + ): void; + } +} /** * Load the Chromecast script, resolving with the global `cast` object. */ export const loadCast = (() => { - let promise: Promise | undefined; + let promise: Promise | undefined; return () => { if (promise === undefined) { diff --git a/web/yarn.lock b/web/yarn.lock index 4c1941f89d..2d6425cb67 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -900,53 +900,21 @@ "@types/node" "*" base-x "^3.0.6" -"@types/chrome@*": - version "0.0.271" - resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.271.tgz#a7b2525291d35137d595b345f70ffde530b2d744" - integrity sha512-K0qgXvkwA5ic+/eygF1xiypHEvCoBgH5lwrhg3yva2mqJuCWyYm0vpZQ22GksAxgGfo0PWev9Zx3plp2clMlwg== - dependencies: - "@types/filesystem" "*" - "@types/har-format" "*" - "@types/chromecast-caf-receiver@^6.0.17": version "6.0.17" resolved "https://registry.yarnpkg.com/@types/chromecast-caf-receiver/-/chromecast-caf-receiver-6.0.17.tgz#c54bb6416ed07b78694db6e953be7ca966d09f06" integrity sha512-vIKyzx7mQglRVJuDl/WE5ehPdAvW4VDShe5I8ewi/8IQjTQkMc1Gx/LjkcpVfxfSSvZ1VKHPbPlJLScgCuAIeg== -"@types/chromecast-caf-sender@^1.0.10": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@types/chromecast-caf-sender/-/chromecast-caf-sender-1.0.10.tgz#b1f9edb2c350e5d1de3dcb7374d28ddb73476166" - integrity sha512-B4iO+T4kMonmvIV+9xyWeIjxNWYVh6RyIQlFUeLk9fgQuXzHtFLnbnVwY7no5qshdUk9szKy0qbCWEMAjMkj4w== - dependencies: - "@types/chrome" "*" - "@types/estree@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/filesystem@*": - version "0.0.36" - resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" - integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA== - dependencies: - "@types/filewriter" "*" - -"@types/filewriter@*": - version "0.0.33" - resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8" - integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g== - "@types/geojson@*": version "7946.0.14" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== -"@types/har-format@*": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.15.tgz#f352493638c2f89d706438a19a9eb300b493b506" - integrity sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA== - "@types/heic-convert@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/heic-convert/-/heic-convert-2.1.0.tgz#d19c7c655abb46321d7a0bc24387d47ac1d1661b" From 9ecb7c4044fd661f4e23824415a70fc59ee05e92 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:04:37 +0530 Subject: [PATCH 08/16] Remove unused --- web/apps/photos/src/utils/cast.ts | 38 ------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 web/apps/photos/src/utils/cast.ts diff --git a/web/apps/photos/src/utils/cast.ts b/web/apps/photos/src/utils/cast.ts deleted file mode 100644 index cd3cdabd05..0000000000 --- a/web/apps/photos/src/utils/cast.ts +++ /dev/null @@ -1,38 +0,0 @@ -declare const chrome: any; -declare const cast: any; - -declare global { - interface Window { - __onGCastApiAvailable: (isAvailable: boolean) => void; - } -} - -/** - * Load the Chromecast script, resolving with the global `cast` object. - */ -export const loadCast = (() => { - let promise: Promise | undefined; - - return () => { - if (promise === undefined) { - promise = new Promise((resolve) => { - const script = document.createElement("script"); - script.src = - "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; - window.__onGCastApiAvailable = (isAvailable) => { - if (isAvailable) { - cast.framework.CastContext.getInstance().setOptions({ - receiverApplicationId: "F5BCEC64", - autoJoinPolicy: - chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, - }); - - resolve(cast); - } - }; - document.body.appendChild(script); - }); - } - return promise; - }; -})(); From a97bb195b6fff9dff36fa100171612988445c34a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:09:33 +0530 Subject: [PATCH 09/16] Update docs We're moving to nanoids --- web/docs/webauthn-passkeys.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/docs/webauthn-passkeys.md b/web/docs/webauthn-passkeys.md index 853a31145e..47b1559629 100644 --- a/web/docs/webauthn-passkeys.md +++ b/web/docs/webauthn-passkeys.md @@ -115,10 +115,10 @@ func (u *PasskeyUser) WebAuthnCredentials() []webauthn.Credential { ##### Response body (JSON) -| Key | Type | Value | -| --------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| options | object | The credential creation options that will be provided to the browser. | -| sessionID | string (uuidv4) | The identifier the server uses to persist metadata about the registration ceremony, like the user ID and challenge to prevent replay attacks. | +| Key | Type | Value | +| --------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| options | object | The credential creation options that will be provided to the browser. | +| sessionID | string (uuid) | The identifier the server uses to persist metadata about the registration ceremony, like the user ID and challenge to prevent replay attacks. | ```json { From 164ace9f8cd49ee5f1ac370af8b17e1b3efc834d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:12:45 +0530 Subject: [PATCH 10/16] Tinker --- .../Collections/AlbumCastDialog.tsx | 64 ++++++++----------- .../src/components/Collections/index.tsx | 6 +- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 1e064eab14..12ace66acc 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -17,19 +17,18 @@ import { Trans } from "react-i18next"; import { v4 as uuidv4 } from "uuid"; interface AlbumCastDialogProps { - show: boolean; - onHide: () => void; - currentCollection: Collection; -} - -enum AlbumCastError { - TV_NOT_FOUND = "tv_not_found", + /** If `true`, the dialog is shown. */ + open: boolean; + /** Callback fired when the dialog wants to be closed. */ + onClose: () => void; + /** The collection that we want to cast. */ + collection: Collection; } export const AlbumCastDialog: React.FC = ({ - show, - onHide, - currentCollection, + open, + onClose, + collection, }) => { const [view, setView] = useState< "choose" | "auto" | "pin" | "auto-cast-error" @@ -50,46 +49,39 @@ export const AlbumCastDialog: React.FC = ({ ) => { try { await doCast(value.trim()); - onHide(); + onClose(); } catch (e) { - const error = e as Error; - let fieldError: string; - switch (error.message) { - case AlbumCastError.TV_NOT_FOUND: - fieldError = t("tv_not_found"); - break; - default: - fieldError = t("UNKNOWN_ERROR"); - break; + if (e instanceof Error && e.message == "tv-not-found") { + setFieldError(t("tv_not_found")); + } else { + setFieldError(t("UNKNOWN_ERROR")); } - - setFieldError(fieldError); } }; const doCast = async (pin: string) => { - // does the TV exist? have they advertised their existence? + // Does the TV exist? have they advertised their existence? const tvPublicKeyB64 = await castGateway.getPublicKey(pin); if (!tvPublicKeyB64) { - throw new Error(AlbumCastError.TV_NOT_FOUND); + throw new Error("tv-not-found"); } - // generate random uuid string + // Generate random id. const castToken = uuidv4(); - // ok, they exist. let's give them the good stuff. + // Ok, they exist. let's give them the good stuff. const payload = JSON.stringify({ castToken: castToken, - collectionID: currentCollection.id, - collectionKey: currentCollection.key, + collectionID: collection.id, + collectionKey: collection.key, }); const encryptedPayload = await boxSeal(btoa(payload), tvPublicKeyB64); - // hey TV, we acknowlege you! + // Hey TV, we acknowlege you! await castGateway.publishCastPayload( pin, encryptedPayload, - currentCollection.id, + collection.id, castToken, ); }; @@ -117,7 +109,7 @@ export const AlbumCastDialog: React.FC = ({ doCast(code) .then(() => { setView("choose"); - onHide(); + onClose(); }) .catch((e) => { log.error("Error casting to TV", e); @@ -127,7 +119,7 @@ export const AlbumCastDialog: React.FC = ({ }, ); - const collectionID = currentCollection.id; + const collectionID = collection.id; session .sendMessage("urn:x-cast:pair-request", { collectionID }) .then(() => { @@ -138,14 +130,14 @@ export const AlbumCastDialog: React.FC = ({ }, [view]); useEffect(() => { - if (show) castGateway.revokeAllTokens(); - }, [show]); + if (open) castGateway.revokeAllTokens(); + }, [open]); return ( = ({ collection={activeCollection} /> ); From 216be38915d725b63eb15c0b63022b820fddb5a9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:23:17 +0530 Subject: [PATCH 11/16] Use same convention for modals --- .../src/components/Collections/index.tsx | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/web/apps/photos/src/components/Collections/index.tsx b/web/apps/photos/src/components/Collections/index.tsx index 98db3c8e1c..c838c7373e 100644 --- a/web/apps/photos/src/components/Collections/index.tsx +++ b/web/apps/photos/src/components/Collections/index.tsx @@ -68,11 +68,11 @@ export const Collections: React.FC = ({ filesDownloadProgressAttributesList, setFilesDownloadProgressAttributesCreator, }) => { - const [allCollectionView, setAllCollectionView] = useState(false); - const [collectionShareModalView, setCollectionShareModalView] = + const [openAllCollectionDialog, setOpenAllCollectionDialog] = useState(false); - - const [showAlbumCastDialog, setShowAlbumCastDialog] = useState(false); + const [openCollectionShareView, setOpenCollectionShareView] = + useState(false); + const [openAlbumCastDialog, setOpenAlbumCastDialog] = useState(false); const [collectionListSortBy, setCollectionListSortBy] = useLocalState( @@ -128,7 +128,7 @@ export const Collections: React.FC = ({ activeCollection={activeCollection} setCollectionNamerAttributes={setCollectionNamerAttributes} showCollectionShareModal={() => - setCollectionShareModalView(true) + setOpenCollectionShareView(true) } setFilesDownloadProgressAttributesCreator={ setFilesDownloadProgressAttributesCreator @@ -137,7 +137,7 @@ export const Collections: React.FC = ({ isActiveCollectionDownloadInProgress } setActiveCollectionID={setActiveCollectionID} - setShowAlbumCastDialog={setShowAlbumCastDialog} + setShowAlbumCastDialog={setOpenAlbumCastDialog} /> ), itemType: ITEM_TYPE.HEADER, @@ -157,11 +157,6 @@ export const Collections: React.FC = ({ return <>; } - const closeAllCollections = () => setAllCollectionView(false); - const openAllCollections = () => setAllCollectionView(true); - const closeCollectionShare = () => setCollectionShareModalView(false); - const closeAlbumCastDialog = () => setShowAlbumCastDialog(false); - return ( <> = ({ collectionListSortBy, setCollectionListSortBy, }} - onShowAllCollections={openAllCollections} + onShowAllCollections={() => setOpenAllCollectionDialog(true)} collectionSummaries={sortedCollectionSummaries.filter((x) => shouldBeShownOnCollectionBar(x.type), )} /> setOpenAllCollectionDialog(false)} collectionSummaries={sortedCollectionSummaries.filter( (x) => !isSystemCollection(x.type), )} @@ -193,18 +188,17 @@ export const Collections: React.FC = ({ collectionListSortBy={collectionListSortBy} isInHiddenSection={mode == "hidden-albums"} /> - setOpenCollectionShareView(false)} collection={activeCollection} /> setOpenAlbumCastDialog(false)} collection={activeCollection} /> From 753ed30d5cfd83b79387be669b586e400c646e2e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:25:13 +0530 Subject: [PATCH 12/16] Tinker --- .../components/Collections/AlbumCastDialog.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 12ace66acc..6680a945c2 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -25,6 +25,9 @@ interface AlbumCastDialogProps { collection: Collection; } +/** + * A dialog that shows various options that the user has for casting an album. + */ export const AlbumCastDialog: React.FC = ({ open, onClose, @@ -135,14 +138,12 @@ export const AlbumCastDialog: React.FC = ({ return ( - {view === "choose" && ( + {view == "choose" && ( <> {browserCanCast && ( <> @@ -161,13 +162,12 @@ export const AlbumCastDialog: React.FC = ({ {t("pair_with_pin_description")} - setView("pin")}> {t("pair_with_pin")} )} - {view === "auto" && ( + {view == "auto" && ( {t("choose_device_from_browser")} @@ -181,7 +181,7 @@ export const AlbumCastDialog: React.FC = ({ )} - {view === "auto-cast-error" && ( + {view == "auto-cast-error" && ( {t("cast_auto_pair_failed")} = ({ )} - {view === "pin" && ( + {view == "pin" && ( <> Date: Sat, 21 Sep 2024 17:35:37 +0530 Subject: [PATCH 13/16] Regular button works for us --- .../Collections/AlbumCastDialog.tsx | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 6680a945c2..4321747661 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -4,13 +4,12 @@ import type { Collection } from "@/media/collection"; import { loadCast } from "@/new/photos/utils/chromecast-sender"; import { VerticallyCentered } from "@ente/shared/components/Container"; import DialogBoxV2 from "@ente/shared/components/DialogBoxV2"; -import EnteButton from "@ente/shared/components/EnteButton"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; import castGateway from "@ente/shared/network/cast"; -import { Link, Typography } from "@mui/material"; +import { Button, Link, Typography } from "@mui/material"; import { t } from "i18next"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; @@ -151,47 +150,37 @@ export const AlbumCastDialog: React.FC = ({ {t("cast_auto_pair_description")} - setView("auto")} > {t("cast_auto_pair")} - + )} {t("pair_with_pin_description")} - setView("pin")}> + )} {view == "auto" && ( {t("choose_device_from_browser")} - { - setView("choose"); - }} - > + )} {view == "auto-cast-error" && ( {t("cast_auto_pair_failed")} - { - setView("choose"); - }} - > + )} {view == "pin" && ( @@ -219,12 +208,9 @@ export const AlbumCastDialog: React.FC = ({ buttonText={t("pair_device_to_tv")} submitButtonProps={{ sx: { mt: 1, mb: 2 } }} /> - setView("choose")} - > + )} From fcf87d237b71ab2c7571b066d6e04505c8e1221a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:39:28 +0530 Subject: [PATCH 14/16] Spacing --- .../Collections/AlbumCastDialog.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 4321747661..45d24f0c73 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -9,7 +9,7 @@ import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; import castGateway from "@ente/shared/network/cast"; -import { Button, Link, Typography } from "@mui/material"; +import { Button, Link, Stack, Typography } from "@mui/material"; import { t } from "i18next"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; @@ -143,28 +143,27 @@ export const AlbumCastDialog: React.FC = ({ sx={{ zIndex: 1600 }} > {view == "choose" && ( - <> + {browserCanCast && ( - <> + {t("cast_auto_pair_description")} - - + )} - - {t("pair_with_pin_description")} - - - + + + {t("pair_with_pin_description")} + + + + )} {view == "auto" && ( From 18c7d59f90619a0f9e9f0ecd6669ff391e181464 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:57:29 +0530 Subject: [PATCH 15/16] Spacing --- .../components/Collections/AlbumCastDialog.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 45d24f0c73..138c3042b4 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -2,7 +2,6 @@ import { boxSeal } from "@/base/crypto/libsodium"; import log from "@/base/log"; import type { Collection } from "@/media/collection"; import { loadCast } from "@/new/photos/utils/chromecast-sender"; -import { VerticallyCentered } from "@ente/shared/components/Container"; import DialogBoxV2 from "@ente/shared/components/DialogBoxV2"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import SingleInputForm, { @@ -166,21 +165,23 @@ export const AlbumCastDialog: React.FC = ({ )} {view == "auto" && ( - - + +
+ +
{t("choose_device_from_browser")} - -
+ )} {view == "auto-cast-error" && ( - + {t("cast_auto_pair_failed")} - - + )} {view == "pin" && ( <> From aae00dcc15aefe700307cfd1090327e0ddded208 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 21 Sep 2024 17:59:41 +0530 Subject: [PATCH 16/16] Doc --- web/packages/base/crypto/libsodium.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 225b5efe72..ff470b1c9e 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -682,6 +682,12 @@ export async function boxSealOpen( ); } +/** + * Encrypt the given {@link input} using the given {@link publicKey}. + * + * This function performs asymmetric (public-key) encryption. To decrypt the + * result, use {@link boxSealOpen}. + */ export async function boxSeal(input: string, publicKey: string) { await sodium.ready; return await toB64(