diff --git a/packages/boost-demo/go.mod b/packages/boost-demo/go.mod new file mode 100644 index 0000000..c5d2e1f --- /dev/null +++ b/packages/boost-demo/go.mod @@ -0,0 +1,3 @@ +module test-server + +go 1.20 diff --git a/packages/boost-demo/main.go b/packages/boost-demo/main.go new file mode 100644 index 0000000..53202be --- /dev/null +++ b/packages/boost-demo/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "bytes" + "io" + "log" + "net/http" + "time" +) + +func main() { + + resp, err := http.Get("http://magmo.com/nitro-protocol.pdf") + if err != nil { + panic(err) + } + + file, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + http.HandleFunc("/nitro-protocol.pdf", func(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT") + w.Header().Set("Access-Control-Allow-Headers", "*") + if r.Method == "OPTIONS" { + _, _ = w.Write([]byte("OK")) + return + } + + http.ServeContent(w, r, "nitro-protocol.pdf", time.Now(), io.NewSectionReader(bytes.NewReader(file), 0, int64(len(file)))) + }) + + log.Fatal(http.ListenAndServe(":8081", nil)) + +} diff --git a/packages/boost-demo/src/App.tsx b/packages/boost-demo/src/App.tsx index aa468f9..169e8f7 100644 --- a/packages/boost-demo/src/App.tsx +++ b/packages/boost-demo/src/App.tsx @@ -1,193 +1,114 @@ -import { ChangeEvent, useEffect, useState } from "react"; -import { NitroRpcClient } from "@statechannels/nitro-rpc-client"; -import { PaymentChannelInfo } from "@statechannels/nitro-rpc-client/src/types"; -import { - Select, - MenuItem, - SelectChangeEvent, - Button, - TextField, - Box, - Table, - TableRow, - TableCell, - TableBody, -} from "@mui/material"; +import { useState } from "react"; +import { Box, Button } from "@mui/material"; import axios, { isAxiosError } from "axios"; -const QUERY_KEY = "rpcUrl"; - import "./App.css"; function App() { - const retrievalProvider = "0xbbb676f9cff8d242e9eac39d063848807d3d1d94"; - const hub = "0x111a00868581f73ab42feef67d235ca09ca1e8db"; - const defaultUrl = "localhost:4005"; - - const url = - new URLSearchParams(window.location.search).get(QUERY_KEY) ?? defaultUrl; + const numChunks = 3; + const length = 403507; - const [nitroClient, setNitroClient] = useState(null); - const [paymentChannels, setPaymentChannels] = useState( - [] - ); - const [selectedChannel, setSelectedChannel] = useState(""); - const [selectedChannelInfo, setSelectedChannelInfo] = useState< - PaymentChannelInfo | undefined - >(); + const [gotChunks, setGotChunks] = useState(new Array(numChunks).fill(false)); - // TODO: For now the default is a hardcoded value based on a local file - // If you're running this locally you'll need to override this value - // Ideally we should just query boost/lotus for the list of available payloads> - const [payloadId, setPayloadId] = useState( - "bafk2bzaceapnitekx4sp3mtitqatm5zpxn6nvjicwtltomttrlof65wlcfjpa" - ); + function setChunkGot(index: number) { + const gotChunksCopy = [...gotChunks]; + gotChunksCopy[index] = true; + setGotChunks(gotChunksCopy); + } const [errorText, setErrorText] = useState(""); - useEffect(() => { - NitroRpcClient.CreateHttpNitroClient(url).then((c) => setNitroClient(c)); - }, [url]); - - // Fetch all the payment channels for the retrieval provider - useEffect(() => { - if (nitroClient) { - // TODO: We should consider adding a API function so this ins't as painful - nitroClient.GetAllLedgerChannels().then((ledgers) => { - for (const l of ledgers) { - if (l.Balance.Hub != hub) continue; - - nitroClient.GetPaymentChannelsByLedger(l.ID).then((payChs) => { - const withProvider = payChs.filter( - (p) => p.Balance.Payee == retrievalProvider - ); - setPaymentChannels(withProvider); - }); - } - }); - } - }, [nitroClient]); - - const updateChannelInfo = async (channelId: string) => { - const paymentChannel = await nitroClient?.GetPaymentChannel(channelId); - setSelectedChannelInfo(paymentChannel); - }; - - const handleSelectedChannelChanged = async (event: SelectChangeEvent) => { - setSelectedChannel(event.target.value); - updateChannelInfo(event.target.value); - }; - - const updatePayloadId = (e: ChangeEvent) => { - setPayloadId(e.target.value); - }; - - const makePayment = () => { - setErrorText(""); - if (nitroClient && selectedChannel) { - nitroClient.Pay(selectedChannel, 100); - // TODO: Slightly hacky but we wait a beat before querying so we see the updated balance - setTimeout(() => { - updateChannelInfo(selectedChannel); - }, 50); + async function getChunk(index: number, file: Int8Array[]) { + if (index >= numChunks) { + throw Error; } - }; - - const fetchFile = () => { - setErrorText(""); - - axios - .get( - `http://localhost:7777/ipfs/${payloadId}?channelId=${selectedChannel}`, + const chunkSize = Math.floor(length / numChunks); + const startByte = index * chunkSize; + const endByte = Math.min(length, (index + 1) * chunkSize); + const range = "bytes=" + startByte + "-" + endByte; + + try { + const result = await axios.get( + `http://localhost:8081/nitro-protocol.pdf`, { responseType: "blob", // This lets us download the file headers: { Accept: "*/*", // TODO: Do we need to specify this? + Range: range, }, } - ) - - .then((result) => { - // This will prompt the browser to download the file - const blob = result.data; - const blobUrl = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = blobUrl; - a.download = "fetched-file-from-ipfs"; - a.click(); - a.remove(); - window.URL.revokeObjectURL(blobUrl); - }) - .catch((e) => { + ); + setChunkGot(index); + file[index] = result.data; + } catch { + // TODO check that the status code is 206 Partial Content + // It could be 200 OK -- but then we may have the entire file already! + (e: any) => { if (isAxiosError(e)) { setErrorText(`${e.message}: ${e.response?.statusText}`); } else { setErrorText(JSON.stringify(e)); } - }); + }; + } + } + + function compileFile(file: Int8Array[]): File { + const f = new File(file, "nitro-protocol.pdf"); + return f; + } + + function triggerCompleteFileDownload(file: Int8Array[]) { + const fileUrl = URL.createObjectURL(compileFile(file)); + const a = document.createElement("a"); + a.href = fileUrl; + a.download = "nitro-protocol.pdf"; + a.click(); + a.remove(); + window.URL.revokeObjectURL(fileUrl); + } + + const fetchFileChunks = async () => { + setErrorText(""); + const file = new Array(numChunks); + for (let i = 0; i < numChunks; i++) { + await getChunk(i, file); + } + + triggerCompleteFileDownload(file); + }; + + const fetchFileConcurrently = async () => { + setErrorText(""); + const file = new Array(numChunks); + const promises = []; + for (let i = 0; i < numChunks; i++) { + promises.push(getChunk(i, file)); + } + await Promise.all(promises); + triggerCompleteFileDownload(file); }; + const fetchFileFull = async () => { + setErrorText(""); + const file = new Array(numChunks); + const result = await axios.get(`http://localhost:8081/nitro-protocol.pdf`, { + responseType: "blob", // This lets us download the file + headers: { + Accept: "*/*", // TODO: Do we need to specify this? + }, + }); + file[0] = result.data; + + triggerCompleteFileDownload(file); + }; return ( - - - - - - - Paid so far - - {selectedChannelInfo && - // TODO: We shouldn't have to cast to a BigInt here, the client should return a BigInt - BigInt(selectedChannelInfo?.Balance.PaidSoFar).toString(10)} - - - - Remaining funds - - {selectedChannelInfo && - // TODO: We shouldn't have to cast to a BigInt here, the client should return a BigInt - BigInt(selectedChannelInfo?.Balance.RemainingFunds).toString( - 10 - )} - - - - Payee - - {selectedChannelInfo && selectedChannelInfo.Balance.Payee} - - - - Payer - - {selectedChannelInfo && selectedChannelInfo.Balance.Payer} - - - -
-
- - - - - - - {errorText} - + {gotChunks.map((c) => (c ? "X" : "_"))} + + + + {errorText}
); }