diff options
| author | Melonai <einebeere@gmail.com> | 2021-07-08 23:29:20 +0200 |
|---|---|---|
| committer | Melonai <einebeere@gmail.com> | 2021-07-08 23:29:20 +0200 |
| commit | c3b05a72b7185112ece6e42c99e9a828c8298f04 (patch) | |
| tree | 317da6fbe640ffa57cf771b4b9e3b0beb80e836c /assets/src | |
| parent | 9d8ca2a653661560f471d717d188e92a79edb250 (diff) | |
| download | rook-c3b05a72b7185112ece6e42c99e9a828c8298f04.tar.zst rook-c3b05a72b7185112ece6e42c99e9a828c8298f04.zip | |
State display for incoming and own requests
Diffstat (limited to 'assets/src')
18 files changed, 356 insertions, 111 deletions
diff --git a/assets/src/components/RequestPage.svelte b/assets/src/components/RequestPage.svelte index eb6b559..d8cf31d 100644 --- a/assets/src/components/RequestPage.svelte +++ b/assets/src/components/RequestPage.svelte @@ -1,21 +1,13 @@ <script lang="ts"> - import { start } from "../network/channel/connection"; - import { startRequest } from "../network/channel/request"; - import Header from "./Header.svelte"; - - start().then(startRequest); + import RequestStatus from "./request/RequestStatus.svelte"; </script> <Header color="white" /> <main> <div class="left-segment"> - <h1>Waiting for a response...</h1> - <p> - The share’s content will become available to you once the sharer - decides to accept your request. - </p> + <RequestStatus /> </div> <div class="right-segment" /> </main> diff --git a/assets/src/components/SharePage.svelte b/assets/src/components/SharePage.svelte index 9be9466..19138ef 100644 --- a/assets/src/components/SharePage.svelte +++ b/assets/src/components/SharePage.svelte @@ -1,20 +1,20 @@ <script lang="ts"> import data from "../stores/data"; import Header from "./Header.svelte"; - import Info from "./share/Info.svelte"; - import Requests from "./share/Requests.svelte"; + import ShareStatus from "./share/ShareStatus.svelte"; + import RequestList from "./share/RequestList.svelte"; </script> <Header color="black" /> <main> <div class="left-segment"> - <Info /> + <ShareStatus /> </div> <div class="right-segment"> {#if $data.locked} <h1>Requests</h1> - <Requests /> + <RequestList /> {/if} </div> </main> diff --git a/assets/src/components/request/RequestStatus.svelte b/assets/src/components/request/RequestStatus.svelte new file mode 100644 index 0000000..b4c9ef9 --- /dev/null +++ b/assets/src/components/request/RequestStatus.svelte @@ -0,0 +1,51 @@ +<script lang="ts"> + import data from "../../stores/data"; + import { + initializeRequest, + OwnRequestState, + } from "../../models/own_request"; + import { startRequestConnection } from "../../network/channel/request_connection"; + + const request = initializeRequest(); + const state = request.state; + + startRequestConnection(request); +</script> + +<!-- TODO: Bind states of same path together --> +{#if $state === OwnRequestState.PENDING || $state === OwnRequestState.ACKNOWLEDGED} + <h1>Waiting for a response...</h1> + <p> + {#if $state === OwnRequestState.ACKNOWLEDGED} + Connecting to signaling server... + {:else} + The share’s content will become available to you once the sharer + decides to accept your request. + {/if} + </p> +{:else if $state === OwnRequestState.IN_FLIGHT || $state === OwnRequestState.DONE} + <h1>Your request was <b>accepted!</b></h1> + {#if $state === OwnRequestState.IN_FLIGHT} + Transferring... + {:else} + <p>Congratulations! You can access the received data below:</p> + <div class="data">{$data.data}</div> + {/if} +{:else if $state === OwnRequestState.DECLINED} + <h1>Your request was <b>declined!</b></h1> + <p>Sorry! I hope we can still be friends?</p> +{:else} + <!-- TODO: Handle specific errors --> + <h1>Eek!</h1> + <p>An error occured during your request.</p> +{/if} + +<style> + .data { + font-size: 14px; + width: 100%; + border: solid 1px #cccccc; + padding: 10px 20px; + box-sizing: border-box; + } +</style> diff --git a/assets/src/components/share/DataSelector.svelte b/assets/src/components/share/DataSelector.svelte new file mode 100644 index 0000000..6838a05 --- /dev/null +++ b/assets/src/components/share/DataSelector.svelte @@ -0,0 +1,40 @@ +<script lang="ts"> + import { startShareConnection } from "../../network/channel/share_connection"; + + import data from "../../stores/data"; + + let value = ""; + + const submit = () => { + data.set(value); + startShareConnection(); + }; + + // TODO: Accept data other than text. +</script> + +<form on:submit|preventDefault={submit}> + <!-- TODO: Prettier input field --> + <input type="text" bind:value /> + <input class="set-data-button" type="submit" value="Share" /> +</form> + +<style> + form { + display: flex; + flex-wrap: nowrap; + } + + input { + border: none; + font-size: 14px; + color: black; + background-color: white; + padding: 10px 20px; + box-sizing: border-box; + } + + .set-data-button { + margin-left: 0.5rem; + } +</style> diff --git a/assets/src/components/share/Request.svelte b/assets/src/components/share/Request.svelte index e34f633..5f9ca7a 100644 --- a/assets/src/components/share/Request.svelte +++ b/assets/src/components/share/Request.svelte @@ -1,31 +1,50 @@ <script lang="ts"> - import { offer } from "../../network/transfer/share"; + import { + acceptIncomingRequest, + declineIncomingRequest, + IncomingRequestState, + } from "../../models/incoming_request"; + import type { IncomingRequest } from "../../models/incoming_request"; import CheckIcon from "../icons/CheckIcon.svelte"; import CloseIcon from "../icons/CloseIcon.svelte"; - export let token: string; + export let request: IncomingRequest; + const state = request.state; async function accept() { - const transfer = await offer(token); + acceptIncomingRequest(request); } - function decline() {} + function decline() { + declineIncomingRequest(request); + } </script> <!-- TODO: Replace placeholder values and show IP instead of token. --> <ul class="request"> - <div class="buttons"> - <div class="round-button" on:click={accept}> - <CheckIcon color="white" /> - </div> - <div class="plain-button" on:click={decline}> - <CloseIcon color="black" /> + {#if $state === IncomingRequestState.WAITING} + <div class="buttons"> + <div class="round-button" on:click={accept}> + <CheckIcon color="white" /> + </div> + <div class="plain-button" on:click={decline}> + <CloseIcon color="black" /> + </div> </div> - </div> + {/if} + <li>Requested at 14:38</li> - <li class="ip">{token}</li> + <li class="ip">{request.info.token}</li> <li>Trusowo, Russia</li> <li>Firefox 89</li> + + {#if $state === IncomingRequestState.IN_FLIGHT} + Transferring... + {:else if $state === IncomingRequestState.DONE} + Done! + {:else if $state === IncomingRequestState.DECLINED} + Declined. + {/if} </ul> <style> diff --git a/assets/src/components/share/Requests.svelte b/assets/src/components/share/RequestList.svelte index 54eab57..423662e 100644 --- a/assets/src/components/share/Requests.svelte +++ b/assets/src/components/share/RequestList.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import requests from "../../stores/requests"; + import requests from "../../stores/received_requests"; import Request from "./Request.svelte"; </script> {#each $requests as request} - <Request token={request} /> -{/each} \ No newline at end of file + <Request {request} /> +{/each} diff --git a/assets/src/components/share/Selector.svelte b/assets/src/components/share/Selector.svelte deleted file mode 100644 index e4c7c07..0000000 --- a/assets/src/components/share/Selector.svelte +++ /dev/null @@ -1,20 +0,0 @@ -<script lang="ts"> - import { start } from "../../network/channel/connection"; - import { startShare } from "../../network/channel/share"; - - import data from "../../stores/data"; - - let value = ""; - - const submit = () => { - data.set(value); - start().then(startShare); - }; - - // TODO: Accept data other than text. -</script> - -<form on:submit|preventDefault={submit}> - <input type="text" bind:value /> - <input type="submit" value="Submit" /> -</form> diff --git a/assets/src/components/share/Info.svelte b/assets/src/components/share/ShareStatus.svelte index 79ef404..15d1ee9 100644 --- a/assets/src/components/share/Info.svelte +++ b/assets/src/components/share/ShareStatus.svelte @@ -5,14 +5,14 @@ getStateStore, } from "../../network/channel/connection"; import data from "../../stores/data"; - import Selector from "./Selector.svelte"; + import DataSelector from "./DataSelector.svelte"; let connection = getStateStore(); </script> {#if !$data.locked} <h1>What do you want to share?</h1> - <Selector /> + <DataSelector /> {:else} <h1> You are <br /> diff --git a/assets/src/models/incoming_request.ts b/assets/src/models/incoming_request.ts new file mode 100644 index 0000000..e1eefb1 --- /dev/null +++ b/assets/src/models/incoming_request.ts @@ -0,0 +1,57 @@ +import { bindTransfer, Transfer } from "../network/transfer/transfer"; +import { Writable, writable } from "svelte/store"; +import { createOfferTransfer } from "../network/transfer/share_transfer"; + +// Represents the current progress of every request +export enum IncomingRequestState { + // Request was neither accepted nor declined yet + WAITING, + // Data is being transferred, more precise data + // is available in the corresponding Transfer object + IN_FLIGHT, + // Requestor has received all data + DONE, + // Request was declined + DECLINED, +} + +// Identifying information about the requestor +export type IncomingRequestInfo = { + token: string; +}; + +// The model for a request received by a sharer +// The state marks changes in the progression of the request lifecycle and can be subscribed to +export type IncomingRequest = { + // Transfer is null while request isn't IN_FLIGHT + transfer: Transfer | null; + info: IncomingRequestInfo; + state: Writable<IncomingRequestState>; +}; + +// Create a model for a new incoming request +export function newIncomingRequest(token: string): IncomingRequest { + const info = { + token, + }; + + return { + transfer: null, + info, + // Each request starts out as just received and waiting for an answer + state: writable(IncomingRequestState.WAITING), + }; +} + +// Starts the transfer of data from the sharer to the requestor +export function acceptIncomingRequest(request: IncomingRequest) { + request.state.set(IncomingRequestState.IN_FLIGHT); + + bindTransfer(request, createOfferTransfer(request.info.token), () => + request.state.set(IncomingRequestState.DONE) + ); +} + +export function declineIncomingRequest(request: IncomingRequest) { + // TODO +} diff --git a/assets/src/models/own_request.ts b/assets/src/models/own_request.ts new file mode 100644 index 0000000..2ad29af --- /dev/null +++ b/assets/src/models/own_request.ts @@ -0,0 +1,40 @@ +import { bindTransfer, Transfer } from "../network/transfer/transfer"; +import { writable, Writable } from "svelte/store"; +import { createAnswerTransfer } from "../network/transfer/request_transfer"; + +// Represents the current progress of the request +export enum OwnRequestState { + PENDING, + ACKNOWLEDGED, + + IN_FLIGHT, + DONE, + + DECLINED, + SHARE_CANCELLED, + NO_SUCH_SHARE, +} + +export type OwnRequest = { + // Transfer is null while request isn't IN_FLIGHT + transfer: Transfer | null; + state: Writable<OwnRequestState>; +}; + +export function initializeRequest(): OwnRequest { + return { + transfer: null, + state: writable(OwnRequestState.PENDING), + }; +} + +export function requestAccepted( + request: OwnRequest, + description: RTCSessionDescriptionInit +) { + request.state.set(OwnRequestState.IN_FLIGHT); + + bindTransfer(request, createAnswerTransfer(description), () => + request.state.set(OwnRequestState.DONE) + ); +} diff --git a/assets/src/network/channel/request.ts b/assets/src/network/channel/request.ts deleted file mode 100644 index 693e408..0000000 --- a/assets/src/network/channel/request.ts +++ /dev/null @@ -1,24 +0,0 @@ -import getShareToken from "../../utils/getShareToken"; -import { answer } from "../transfer/request"; -import { Connection, ConnectionState, on, updateState } from "./connection"; -import type { RequestAcceptedMessage } from "./messages/messages"; -import { joinRequestChannel } from "./socket"; - -export async function startRequest(connection: Connection) { - updateState(ConnectionState.CONNECTING_CHANNEL); - - const requestChannel = await joinRequestChannel( - connection.socket, - connection.token, - getShareToken() - ); - connection.channel = requestChannel; - - on("request_accepted", onRequestAccepted); - - updateState(ConnectionState.CONNECTED); -} - -async function onRequestAccepted(message: RequestAcceptedMessage) { - await answer(message); -} diff --git a/assets/src/network/channel/request_connection.ts b/assets/src/network/channel/request_connection.ts new file mode 100644 index 0000000..1b873d3 --- /dev/null +++ b/assets/src/network/channel/request_connection.ts @@ -0,0 +1,48 @@ +import { + requestAccepted, + OwnRequest, + OwnRequestState, +} from "../../models/own_request"; +import getShareToken from "../../utils/getShareToken"; +import { ConnectionState, on, start, updateState } from "./connection"; +import type { + RequestAcceptedMessage, + ShareCancelledMessage, +} from "./messages/messages"; +import { joinRequestChannel } from "./socket"; + +export async function startRequestConnection(ownRequest: OwnRequest) { + const connection = await start(); + + updateState(ConnectionState.CONNECTING_CHANNEL); + + const requestChannel = await joinRequestChannel( + connection.socket, + connection.token, + getShareToken() + ); + connection.channel = requestChannel; + + on("request_accepted", (message: RequestAcceptedMessage) => + onRequestAccepted(message, ownRequest) + ); + + on("share_cancelled", (message: ShareCancelledMessage) => + onShareCancelled(message, ownRequest) + ); + + updateState(ConnectionState.CONNECTED); +} + +// Events which can happen without prior triggers during a request's lifetime + +function onRequestAccepted( + message: RequestAcceptedMessage, + request: OwnRequest +) { + requestAccepted(request, message); +} + +function onShareCancelled(message: ShareCancelledMessage, request: OwnRequest) { + request.state.set(OwnRequestState.SHARE_CANCELLED); +} \ No newline at end of file diff --git a/assets/src/network/channel/share.ts b/assets/src/network/channel/share_connection.ts index 81942af..415cb7f 100644 --- a/assets/src/network/channel/share.ts +++ b/assets/src/network/channel/share_connection.ts @@ -1,9 +1,10 @@ -import requests from "../../stores/requests"; +import { newIncomingRequest } from "../../models/incoming_request"; +import requests from "../../stores/received_requests"; import { - Connection, ConnectionState, on, onWithToken, + start, updateState, } from "./connection"; import type { UnregisterHandler } from "./messages/handler"; @@ -13,7 +14,9 @@ import type { } from "./messages/messages"; import { joinShareChannel } from "./socket"; -export async function startShare(connection: Connection) { +export async function startShareConnection() { + const connection = await start(); + updateState(ConnectionState.CONNECTING_CHANNEL); const shareChannel = await joinShareChannel( @@ -27,10 +30,13 @@ export async function startShare(connection: Connection) { updateState(ConnectionState.CONNECTED); } +// Events which can happen without prior triggers during a share's lifetime + function onNewRequest(message: NewRequestMessage) { const token = message.token; - requests.addRequest(token); + const request = newIncomingRequest(token); + requests.addRequest(request); onWithToken("request_cancelled", token, onRequestCancelled); } diff --git a/assets/src/network/transfer/request.ts b/assets/src/network/transfer/request_transfer.ts index 920cd9a..317f5e5 100644 --- a/assets/src/network/transfer/request.ts +++ b/assets/src/network/transfer/request_transfer.ts @@ -1,17 +1,17 @@ +import data from "../../stores/data"; import { on, send } from "../channel/connection"; import type { RequestIceCandidateMessage } from "../channel/messages/messages"; import { createTransfer, onIncomingIceCandidate, Transfer, - TransferType, unregisterIceOnComplete, } from "./transfer"; -export async function answer( +export async function createAnswerTransfer( offer: RTCSessionDescriptionInit ): Promise<Transfer> { - const transfer = createTransfer(TransferType.ANSWER, onChannel); + const transfer = createTransfer(onChannel); const offerDescription = new RTCSessionDescription(offer); transfer.pc.setRemoteDescription(offerDescription); @@ -42,8 +42,10 @@ export async function answer( return transfer; } -function onChannel(channel: RTCDataChannel) { +function onChannel(channel: RTCDataChannel, completeTransfer: () => void) { channel.onmessage = event => { - console.log(event.data) - } + data.set(event.data); + // TODO: Disconnect from channel + completeTransfer(); + }; } diff --git a/assets/src/network/transfer/share.ts b/assets/src/network/transfer/share_transfer.ts index 5e43df0..fc6df9c 100644 --- a/assets/src/network/transfer/share.ts +++ b/assets/src/network/transfer/share_transfer.ts @@ -10,12 +10,13 @@ import { createTransfer, onIncomingIceCandidate, Transfer, - TransferType, unregisterIceOnComplete, } from "./transfer"; -export async function offer(request_token: string): Promise<Transfer> { - const transfer = createTransfer(TransferType.OFFER, onChannel); +export async function createOfferTransfer( + request_token: string +): Promise<Transfer> { + const transfer = createTransfer(onChannel); const offer = await transfer.pc.createOffer(); transfer.pc.setLocalDescription(offer); @@ -65,7 +66,9 @@ function onShareAccepted( unregister(); } -function onChannel(channel: RTCDataChannel) { +function onChannel(channel: RTCDataChannel, completeTransfer: () => void) { const data = get(dataStore).data; channel.send(data); + // TODO: Add retransmission possibility in case of transfer failure? + completeTransfer(); } diff --git a/assets/src/network/transfer/transfer.ts b/assets/src/network/transfer/transfer.ts index 976d113..7641c17 100644 --- a/assets/src/network/transfer/transfer.ts +++ b/assets/src/network/transfer/transfer.ts @@ -1,18 +1,22 @@ +import { Writable, writable } from "svelte/store"; +import type { IncomingRequest } from "../../models/incoming_request"; +import type { OwnRequest } from "../../models/own_request"; import type { UnregisterHandler } from "../channel/messages/handler"; import type { RequestIceCandidateMessage, ShareIceCandidateMessage, } from "../channel/messages/messages"; -export enum TransferType { - OFFER, - ANSWER, +export enum TransferState { + CONNECTING, + TRANSFERRING, + DONE, } export type Transfer = { pc: RTCPeerConnection; channel: RTCDataChannel; - type: TransferType; + state: Writable<TransferState>; }; const servers = { @@ -28,24 +32,48 @@ const servers = { }; export function createTransfer( - type: TransferType, - onChannel: (channel: RTCDataChannel) => void + onChannel: (channel: RTCDataChannel, completeTransfer: () => void) => void ): Transfer { const pc = new RTCPeerConnection(servers); const channel = pc.createDataChannel("channel", { negotiated: true, id: 0, }); + const state = writable(TransferState.CONNECTING); - channel.onopen = () => onChannel(channel); + channel.onopen = () => { + state.set(TransferState.TRANSFERRING); + const completeTransfer = () => state.set(TransferState.DONE); + onChannel(channel, completeTransfer); + }; return { pc, channel, - type, + state, }; } +export function bindTransfer( + request: OwnRequest | IncomingRequest, + transferPromise: Promise<Transfer>, + onTransferComplete: () => void +) { + transferPromise.then(transfer => { + request.transfer = transfer; + + const unsubsribe = transfer.state.subscribe(transferState => { + if (transferState === TransferState.DONE) { + unsubsribe(); + // Once the data has been transferred we can remove the transfer + request.transfer = null; + + onTransferComplete(); + } + }); + }); +} + export function onIncomingIceCandidate( transfer: Transfer, message: ShareIceCandidateMessage | RequestIceCandidateMessage diff --git a/assets/src/stores/received_requests.ts b/assets/src/stores/received_requests.ts new file mode 100644 index 0000000..48916ad --- /dev/null +++ b/assets/src/stores/received_requests.ts @@ -0,0 +1,17 @@ +import { writable } from "svelte/store"; +import type { IncomingRequest } from "../models/incoming_request"; + +const createRequestStore = () => { + const { subscribe, update } = writable<IncomingRequest[]>([]); + + return { + subscribe, + addRequest: (request: IncomingRequest) => update(state => [request, ...state]), + removeRequest: (token: string) => + update(state => + state.filter(request => request.info.token !== token) + ), + }; +}; + +export default createRequestStore(); diff --git a/assets/src/stores/requests.ts b/assets/src/stores/requests.ts deleted file mode 100644 index 1dc8cb2..0000000 --- a/assets/src/stores/requests.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { writable } from "svelte/store"; - -const createRequestStore = () => { - const { subscribe, update } = writable<string[]>([]); - - return { - subscribe, - addRequest: request => update(state => [request, ...state]), - removeRequest: request => - update(state => state.filter(r => r !== request)), - }; -}; - -export default createRequestStore(); |
