diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index db2009b662..322ca395bb 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -4,7 +4,10 @@ import { boxSeal } from "@/base/crypto"; import log from "@/base/log"; import type { Collection } from "@/media/collection"; import { photosDialogZIndex } from "@/new/photos/components/utils/z-index"; -import castGateway, { revokeAllCastTokens } from "@/new/photos/services/cast"; +import castGateway, { + publicKeyForPairingCode, + revokeAllCastTokens, +} from "@/new/photos/services/cast"; import { loadCast } from "@/new/photos/utils/chromecast-sender"; import SingleInputForm, { type SingleInputFormProps, @@ -52,36 +55,34 @@ export const AlbumCastDialog: React.FC = ({ setFieldError, ) => { try { - await doCast(value.trim()); - onClose(); - } catch (e) { - if (e instanceof Error && e.message == "tv-not-found") { - setFieldError(t("tv_not_found")); + if (await doCast(value.trim())) { + onClose(); } else { - setFieldError(t("generic_error_retry")); + setFieldError(t("tv_not_found")); } + } catch (e) { + log.error("Failed to cast", e); + setFieldError(t("generic_error_retry")); } }; const doCast = async (pin: string) => { - // Does the TV exist? have they advertised their existence? - const tvPublicKeyB64 = await castGateway.getPublicKey(pin); - if (!tvPublicKeyB64) { - throw new Error("tv-not-found"); - } + // Find out the public key associated with the given pairing code (if + // indeed a device has published one). + const publicKey = await publicKeyForPairingCode(pin); + if (!publicKey) return false; // Generate random id. const castToken = uuidv4(); - // Ok, they exist. let's give them the good stuff. + // Publish the payload so that the other end can use it. const payload = JSON.stringify({ castToken: castToken, collectionID: collection.id, collectionKey: collection.key, }); - const encryptedPayload = await boxSeal(btoa(payload), tvPublicKeyB64); + const encryptedPayload = await boxSeal(btoa(payload), publicKey); - // Hey TV, we acknowlege you! await castGateway.publishCastPayload( pin, encryptedPayload, diff --git a/web/packages/new/photos/services/cast.ts b/web/packages/new/photos/services/cast.ts index e145c96c1e..3de997539e 100644 --- a/web/packages/new/photos/services/cast.ts +++ b/web/packages/new/photos/services/cast.ts @@ -1,9 +1,8 @@ -import { authenticatedRequestHeaders } from "@/base/http"; -import log from "@/base/log"; +import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; import { apiURL } from "@/base/origins"; -import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; +import { z } from "zod"; /** * Revoke all existing outstanding cast tokens for the current user on remote. @@ -14,28 +13,22 @@ export const revokeAllCastTokens = async () => headers: await authenticatedRequestHeaders(), }); -class CastGateway { - public async getPublicKey(code: string): Promise { - let resp; - try { - const token = getToken(); - resp = await HTTPService.get( - await apiURL(`/cast/device-info/${code}`), - undefined, - { - "X-Auth-Token": token, - }, - ); - } catch (e) { - if (e instanceof ApiError && e.httpStatusCode === 404) { - return ""; - } - log.error("failed to getPublicKey", e); - throw e; - } - return resp.data.publicKey; - } +/** + * Fetch the public key (represented as a base64 string) associated with the + * given device / pairing {@link code} from remote, or `undefined` if there is + * no public key associated with the given code. + */ +export const publicKeyForPairingCode = async (code: string) => { + const res = await fetch(await apiURL(`/cast/device-info/${code}`), { + headers: await authenticatedRequestHeaders(), + }); + if (res.status == 404) return undefined; + ensureOk(res); + return z.object({ publicKey: z.string() }).parse(await res.json()) + .publicKey; +}; +class CastGateway { public async publishCastPayload( code: string, castPayload: string,