diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index c89313d10e..74cb384a16 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -5,7 +5,7 @@ import { PairingCode } from "components/PairingCode"; 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 { getCastPayload, register } from "services/pair"; import { advertiseOnChromecast } from "../services/chromecast-receiver"; export default function Index() { @@ -35,9 +35,7 @@ export default function Index() { const pollTick = async () => { try { - // TODO: - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const data = await getCastData({ + const data = await getCastPayload({ publicKey, privateKey, pairingCode, diff --git a/web/apps/cast/src/services/cast-data.ts b/web/apps/cast/src/services/cast-data.ts index 85e9dd8451..700f1ca860 100644 --- a/web/apps/cast/src/services/cast-data.ts +++ b/web/apps/cast/src/services/cast-data.ts @@ -1,3 +1,5 @@ +import type { CastPayload } from "./pair"; + export interface CastData { /** The ID of the callection we are casting. */ collectionID: string; @@ -12,7 +14,7 @@ export interface CastData { * * We will read in back when we start the slideshow. */ -export const storeCastData = (payload: unknown) => { +export const storeCastData = (payload: CastPayload | undefined) => { if (!payload || typeof payload != "object") throw new Error("Unexpected cast data"); diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index e10e6ab5ed..1afe6fd7fc 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -63,7 +63,7 @@ export interface Registration { * Once the client gets the pairing code (via Chromecast or manual entry), * they'll let museum know. So in parallel with Phase 2, we perform Phase 3. * - * Phase 3 - {@link getCastData} in a setInterval. + * Phase 3 - {@link getCastPayload} in a setInterval. * * 7. Keep polling museum to ask it if anyone has claimed that code we vended * out and used that to send us an payload encrypted using our public key. @@ -78,7 +78,7 @@ export interface Registration { * At this time we start showing the pairing code on the UI, and start phase 2, * {@link advertiseCode} to vend out the pairing code to Chromecast connections. * - * In parallel, we start Phase 3, calling {@link getCastData} in a loop. Once we + * In parallel, we start Phase 3, calling {@link getCastPayload} in a loop. Once we * get a response, we decrypt it to get the data we need to start the slideshow. */ export const register = async (): Promise => { @@ -119,6 +119,18 @@ const registerDevice = async (publicKey: string) => { .deviceCode; }; +/** + * The structure of the (decrypted) payload that is published (e.g.) by + * `publishCastPayload` on the photos web/desktop app. + */ +const CastPayload = z.object({ + castToken: z.string(), + collectionID: z.number(), + collectionKey: z.string(), +}); + +export type CastPayload = z.infer; + /** * Ask museum if anyone has sent a (encrypted) payload corresponding to the * given pairing code. If so, decrypt it using our private key and return the @@ -128,13 +140,15 @@ const registerDevice = async (publicKey: string) => { * * See: [Note: Pairing protocol]. */ -export const getCastData = async (registration: Registration) => { +export const getCastPayload = async ( + registration: Registration, +): Promise => { const { pairingCode, publicKey, privateKey } = registration; // The client will send us the encrypted payload using our public key that // we registered with museum. const encryptedCastData = await getEncryptedCastData(pairingCode); - if (!encryptedCastData) return; + if (!encryptedCastData) return undefined; // Decrypt it using the private key of the pair and return the plaintext // payload, which'll be a JSON object containing the data we need to start a @@ -145,9 +159,7 @@ export const getCastData = async (registration: Registration) => { privateKey, ); - // TODO: - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse(atob(decryptedCastData)); + return CastPayload.parse(JSON.parse(atob(decryptedCastData))); }; /**