diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 59df251db..6a5afc2e1 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -1,176 +1,164 @@ -import log from "@/next/log"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; -import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium"; -import castGateway from "@ente/shared/network/cast"; import LargeType from "components/LargeType"; -import _sodium from "libsodium-wrappers"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +import { pair, register, type Registration } from "services/cast"; import { storeCastData } from "services/cast/castService"; import { useCastReceiver } from "../utils/useCastReceiver"; export default function PairingMode() { + const [registration, setRegistration] = useState< + Registration | undefined + >(); const [deviceCode, setDeviceCode] = useState(""); - const [publicKeyB64, setPublicKeyB64] = useState(""); - const [privateKeyB64, setPrivateKeyB64] = useState(""); - const [codePending, setCodePending] = useState(true); - const [isCastReady, setIsCastReady] = useState(false); const cast = useCastReceiver(); - useEffect(() => { - init(); - }, []); + // useEffect(() => { + // init(); + // }, []); - const init = async () => { - try { - const keypair = await generateKeyPair(); - setPublicKeyB64(await toB64(keypair.publicKey)); - setPrivateKeyB64(await toB64(keypair.privateKey)); - } catch (e) { - log.error("failed to generate keypair", e); - throw e; - } - }; + // const init = async () => { + // try { + // const keypair = await generateKeyPair(); + // setPublicKeyB64(await toB64(keypair.publicKey)); + // setPrivateKeyB64(await toB64(keypair.privateKey)); + // } catch (e) { + // log.error("failed to generate keypair", e); + // throw e; + // } + // }; - useEffect(() => { - if (!cast) { - return; - } - if (isCastReady) { - return; - } - const context = cast.framework.CastReceiverContext.getInstance(); + // useEffect(() => { + // if (!cast) { + // return; + // } + // if (isCastReady) { + // return; + // } + // const context = cast.framework.CastReceiverContext.getInstance(); - try { - const options = new cast.framework.CastReceiverOptions(); - options.maxInactivity = 3600; - options.customNamespaces = Object.assign({}); - options.customNamespaces["urn:x-cast:pair-request"] = - cast.framework.system.MessageType.JSON; + // try { + // const options = new cast.framework.CastReceiverOptions(); + // options.maxInactivity = 3600; + // options.customNamespaces = Object.assign({}); + // options.customNamespaces["urn:x-cast:pair-request"] = + // cast.framework.system.MessageType.JSON; - options.disableIdleTimeout = true; - context.set; + // options.disableIdleTimeout = true; + // context.set; - context.addCustomMessageListener( - "urn:x-cast:pair-request", - messageReceiveHandler, - ); + // context.addCustomMessageListener( + // "urn:x-cast:pair-request", + // messageReceiveHandler, + // ); - // listen to close request and stop the context - context.addEventListener( - cast.framework.system.EventType.SENDER_DISCONNECTED, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - (_) => { - context.stop(); - }, - ); - context.start(options); - setIsCastReady(true); - } catch (e) { - log.error("failed to create cast context", e); - } + // // listen to close request and stop the context + // context.addEventListener( + // cast.framework.system.EventType.SENDER_DISCONNECTED, + // // eslint-disable-next-line @typescript-eslint/no-unused-vars + // (_) => { + // context.stop(); + // }, + // ); + // context.start(options); + // setIsCastReady(true); + // } catch (e) { + // log.error("failed to create cast context", e); + // } - return () => { - // context.stop(); - }; - }, [cast]); + // return () => { + // // context.stop(); + // }; + // }, [cast]); - const messageReceiveHandler = (message: { - type: string; - senderId: string; - data: any; - }) => { - try { - cast.framework.CastReceiverContext.getInstance().sendCustomMessage( - "urn:x-cast:pair-request", - message.senderId, - { - code: deviceCode, - }, - ); - } catch (e) { - log.error("failed to send message", e); - } - }; + // const messageReceiveHandler = (message: { + // type: string; + // senderId: string; + // data: any; + // }) => { + // try { + // cast.framework.CastReceiverContext.getInstance().sendCustomMessage( + // "urn:x-cast:pair-request", + // message.senderId, + // { + // code: deviceCode, + // }, + // ); + // } catch (e) { + // log.error("failed to send message", e); + // } + // }; - const generateKeyPair = async () => { - await _sodium.ready; - const keypair = _sodium.crypto_box_keypair(); - return keypair; - }; + // const generateKeyPair = async () => { + // await _sodium.ready; + // const keypair = _sodium.crypto_box_keypair(); + // return keypair; + // }; - const pollForCastData = async () => { - if (codePending) { - return; - } - // see if we were acknowledged on the client. - // the client will send us the encrypted payload using our public key that we advertised. - // then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow. - let devicePayload = ""; - try { - const encDastData = await castGateway.getCastData(`${deviceCode}`); - if (!encDastData) return; - devicePayload = encDastData; - } catch (e) { - setCodePending(true); - init(); - return; - } + // const pollForCastData = async () => { + // if (codePending) { + // return; + // } + // // see if we were acknowledged on the client. + // // the client will send us the encrypted payload using our public key that we advertised. + // // then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow. + // let devicePayload = ""; + // try { + // const encDastData = await castGateway.getCastData(`${deviceCode}`); + // if (!encDastData) return; + // devicePayload = encDastData; + // } catch (e) { + // setCodePending(true); + // init(); + // return; + // } - const decryptedPayload = await boxSealOpen( - devicePayload, - publicKeyB64, - privateKeyB64, - ); + // const decryptedPayload = await boxSealOpen( + // devicePayload, + // publicKeyB64, + // privateKeyB64, + // ); - const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); + // const decryptedPayloadObj = JSON.parse(atob(decryptedPayload)); - return decryptedPayloadObj; - }; + // return decryptedPayloadObj; + // }; - const advertisePublicKey = async (publicKeyB64: string) => { - // hey client, we exist! - try { - const codeValue = await castGateway.registerDevice(publicKeyB64); - setDeviceCode(codeValue); - setCodePending(false); - } catch (e) { - // schedule re-try after 5 seconds - setTimeout(() => { - init(); - }, 5000); - return; - } - }; + // const advertisePublicKey = async (publicKeyB64: string) => { + // // hey client, we exist! + // try { + // const codeValue = await castGateway.registerDevice(publicKeyB64); + // setDeviceCode(codeValue); + // setCodePending(false); + // } catch (e) { + // // schedule re-try after 5 seconds + // setTimeout(() => { + // init(); + // }, 5000); + // return; + // } + // }; const router = useRouter(); useEffect(() => { - console.log("useEffect for pairing called"); - if (deviceCode.length < 1 || !publicKeyB64 || !privateKeyB64) return; - - const interval = setInterval(async () => { - console.log("polling for cast data"); - const data = await pollForCastData(); - if (!data) { - console.log("no data"); - return; - } - storeCastData(data); - console.log("pushing slideshow"); - await router.push("/slideshow"); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, [deviceCode, publicKeyB64, privateKeyB64, codePending]); + register().then((r) => setRegistration(r)); + }, []); useEffect(() => { - if (!publicKeyB64) return; - advertisePublicKey(publicKeyB64); - }, [publicKeyB64]); + if (!cast || !registration) return; + + pair(cast, registration).then((data) => { + storeCastData(data); + router.push("/slideshow"); + }); + }, [cast, registration]); + + // useEffect(() => { + // if (!publicKeyB64) return; + // advertisePublicKey(publicKeyB64); + // }, [publicKeyB64]); return ( <> @@ -204,12 +192,10 @@ export default function PairingMode() { overflow: "hidden", }} > - {codePending ? ( - + {deviceCode ? ( + ) : ( - <> - - + )}

{ +export const register = async (): Promise => { // Generate keypair. const keypair = await generateKeyPair(); const publicKeyB64 = await toB64(keypair.publicKey); const privateKeyB64 = await toB64(keypair.privateKey); // Register keypair with museum to get a pairing code. - let code: string; + let pairingCode: string; do { try { - code = await castGateway.registerDevice(publicKeyB64); + pairingCode = await castGateway.registerDevice(publicKeyB64); } catch (e) { log.error("Failed to register public key with server", e); // Schedule retry after 10 seconds. await wait(10000); } - } while (code === undefined); + } while (pairingCode === undefined); + + return { pairingCode, publicKeyB64, privateKeyB64 }; +}; + +/** + * Listen for pairing requests using the given {@link cast} instance for + * connections for {@link registration}. Phase 2 of the pairing protocol. + * + * On successful pairing, return the payload (JSON) sent by the sender who + * connected to us. See: [Note: Pairing protocol] + */ +export const pair = async (cast: Cast, registration: Registration) => { + const { pairingCode, publicKeyB64, privateKeyB64 } = registration; // Prepare the Chromecast "context". const context = cast.framework.CastReceiverContext.getInstance(); @@ -76,7 +104,7 @@ export const pair = async (cast: Cast) => { // Reply with the code that we have if anyone asks over chromecast. const incomingMessageListener = ({ senderId }: { senderId: string }) => - context.sendCustomMessage(namespace, senderId, { code }); + context.sendCustomMessage(namespace, senderId, { code: pairingCode }); context.addCustomMessageListener( namespace, @@ -104,7 +132,7 @@ export const pair = async (cast: Cast) => { // a JSON object containing the data we need to start a slideshow for // some collection. try { - encryptedCastData = await castGateway.getCastData(code); + encryptedCastData = await castGateway.getCastData(pairingCode); } catch (e) { log.error("Failed to get cast data from server", e); // Schedule retry after 10 seconds.