diff options
| author | Mel <einebeere@gmail.com> | 2022-02-19 20:00:38 +0100 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2022-02-19 20:00:38 +0100 |
| commit | bf1450799df0deb424a9675be89e13c29e3620d7 (patch) | |
| tree | 6a5f2f7559c5946058deadf8375f6609485a3d3f /assets/src | |
| parent | 5384c34952b031995ecb8aa58d72954b0c685e18 (diff) | |
| download | rook-bf1450799df0deb424a9675be89e13c29e3620d7.tar.zst rook-bf1450799df0deb424a9675be89e13c29e3620d7.zip | |
Split state into stages to handle messages
Diffstat (limited to 'assets/src')
| -rw-r--r-- | assets/src/components/SharePage.svelte | 6 | ||||
| -rw-r--r-- | assets/src/components/request/RequestStatus.svelte | 21 | ||||
| -rw-r--r-- | assets/src/components/share/DataSelector.svelte | 8 | ||||
| -rw-r--r-- | assets/src/components/share/Request.svelte | 13 | ||||
| -rw-r--r-- | assets/src/components/share/RequestList.svelte | 16 | ||||
| -rw-r--r-- | assets/src/components/share/ShareStatus.svelte | 26 | ||||
| -rw-r--r-- | assets/src/entries/request.ts | 2 | ||||
| -rw-r--r-- | assets/src/entries/share.ts | 2 | ||||
| -rw-r--r-- | assets/src/models/incoming_request.ts | 16 | ||||
| -rw-r--r-- | assets/src/models/own_request.ts | 40 | ||||
| -rw-r--r-- | assets/src/network/transfer/request_transfer.ts | 35 | ||||
| -rw-r--r-- | assets/src/network/transfer/share_transfer.ts | 52 | ||||
| -rw-r--r-- | assets/src/network/transfer/transfer.ts | 46 | ||||
| -rw-r--r-- | assets/src/state/received_requests.ts | 17 | ||||
| -rw-r--r-- | assets/src/state/request.ts | 153 | ||||
| -rw-r--r-- | assets/src/state/share.ts | 172 | ||||
| -rw-r--r-- | assets/src/utils/bind.ts | 3 |
17 files changed, 411 insertions, 217 deletions
diff --git a/assets/src/components/SharePage.svelte b/assets/src/components/SharePage.svelte index dff6ba0..c0c6f18 100644 --- a/assets/src/components/SharePage.svelte +++ b/assets/src/components/SharePage.svelte @@ -1,8 +1,10 @@ <script lang="ts"> - import data from "../stores/data"; import Header from "./Header.svelte"; import ShareStatus from "./share/ShareStatus.svelte"; import RequestList from "./share/RequestList.svelte"; + import { getShareState, ShareStateType } from "../state/share"; + + const state = getShareState().type; </script> <Header color="black" /> @@ -12,7 +14,7 @@ <ShareStatus /> </div> <div class="right-segment"> - {#if $data.locked} + {#if $state === ShareStateType.SHARING} <h1>Requests</h1> <RequestList /> {/if} diff --git a/assets/src/components/request/RequestStatus.svelte b/assets/src/components/request/RequestStatus.svelte index 49a0c31..7ba93d9 100644 --- a/assets/src/components/request/RequestStatus.svelte +++ b/assets/src/components/request/RequestStatus.svelte @@ -1,37 +1,30 @@ <script lang="ts"> - import { - initializeRequest, - OwnRequestState, - } from "../../models/own_request"; - import { startRequestConnection } from "../../network/channel/request_connection"; + import { getRequestState, RequestStateType } from "../../state/request"; import DataView from "../DataView.svelte"; - const request = initializeRequest(); - const state = request.state; - - startRequestConnection(request); + const state = getRequestState().type; </script> <!-- TODO: Bind states of same path together --> -{#if $state === OwnRequestState.PENDING || $state === OwnRequestState.ACKNOWLEDGED} +{#if $state === RequestStateType.CONNECTING || $state === RequestStateType.WAITING_FOR_RESPONSE} <h1>Waiting for a response...</h1> <p> - {#if $state === OwnRequestState.ACKNOWLEDGED} + {#if $state === RequestStateType.CONNECTING} 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} +{:else if $state === RequestStateType.IN_FLIGHT || $state === RequestStateType.DONE} <h1>Your request was <b>accepted!</b></h1> - {#if $state === OwnRequestState.IN_FLIGHT} + {#if $state === RequestStateType.IN_FLIGHT} Transferring... {:else} <p>Congratulations! You can access the received data below:</p> <DataView /> {/if} -{:else if $state === OwnRequestState.DECLINED} +{:else if $state === RequestStateType.DECLINED} <h1>Your request was <b>declined!</b></h1> <p>Sorry! I hope we can still be friends?</p> {:else} diff --git a/assets/src/components/share/DataSelector.svelte b/assets/src/components/share/DataSelector.svelte index 6838a05..965d0a1 100644 --- a/assets/src/components/share/DataSelector.svelte +++ b/assets/src/components/share/DataSelector.svelte @@ -1,13 +1,11 @@ <script lang="ts"> - import { startShareConnection } from "../../network/channel/share_connection"; - - import data from "../../stores/data"; + import { ChoosingData, getShareState } from "../../state/share"; let value = ""; const submit = () => { - data.set(value); - startShareConnection(); + const share = getShareState().state as ChoosingData; + share.submitData(value); }; // TODO: Accept data other than text. diff --git a/assets/src/components/share/Request.svelte b/assets/src/components/share/Request.svelte index e554bfe..40a62fc 100644 --- a/assets/src/components/share/Request.svelte +++ b/assets/src/components/share/Request.svelte @@ -1,12 +1,9 @@ <script lang="ts"> - import { - acceptIncomingRequest, - declineIncomingRequest, - IncomingRequestState, - } from "../../models/incoming_request"; + import { IncomingRequestState } from "../../models/incoming_request"; import type { IncomingRequest } from "../../models/incoming_request"; import CheckIcon from "../icons/CheckIcon.svelte"; import CloseIcon from "../icons/CloseIcon.svelte"; + import { getShareState, Sharing } from "../../state/share"; export let request: IncomingRequest; const state = request.state; @@ -14,11 +11,13 @@ const time = `${request.info.receivedAt.getHours()}:${request.info.receivedAt.getMinutes()}`; async function accept() { - acceptIncomingRequest(request); + const sharing = getShareState().state as Sharing; + sharing.acceptRequest(request); } function decline() { - declineIncomingRequest(request); + const sharing = getShareState().state as Sharing; + sharing.declineRequest(request); } </script> diff --git a/assets/src/components/share/RequestList.svelte b/assets/src/components/share/RequestList.svelte index 510e80c..1a3b715 100644 --- a/assets/src/components/share/RequestList.svelte +++ b/assets/src/components/share/RequestList.svelte @@ -1,6 +1,20 @@ <script lang="ts"> - import requests from "../../stores/received_requests"; + import { derived } from "svelte/store"; + import type { Readable } from "svelte/store"; + import type { IncomingRequest } from "../../models/incoming_request"; + import { getShareState, Sharing } from "../../state/share"; import Request from "./Request.svelte"; + + const sharing = getShareState().state as Sharing; + const requestMap = sharing.getRequests(); + + function requestSorter(a: IncomingRequest, b: IncomingRequest): number { + return a.info.receivedAt.getTime() - b.info.receivedAt.getTime(); + } + + const requests: Readable<IncomingRequest[]> = derived(requestMap, $map => { + return Object.values($map).sort(requestSorter); + }); </script> {#each $requests as request (request.info.token)} diff --git a/assets/src/components/share/ShareStatus.svelte b/assets/src/components/share/ShareStatus.svelte index 7c22a04..ba0fea0 100644 --- a/assets/src/components/share/ShareStatus.svelte +++ b/assets/src/components/share/ShareStatus.svelte @@ -1,17 +1,17 @@ <script lang="ts"> - import { - ConnectionState, - getOwnToken, - getStateStore, - } from "../../network/channel/connection"; - import data from "../../stores/data"; - import DataView from "../DataView.svelte"; + import { getShareState, ShareStateType, Sharing } from "../../state/share"; import DataSelector from "./DataSelector.svelte"; + import DataView from "../DataView.svelte"; - let connection = getStateStore(); + const state = getShareState().type; + + function token() { + const sharing = getShareState().state as Sharing; + return sharing.getToken(); + } </script> -{#if !$data.locked} +{#if $state == ShareStateType.CHOOSING_DATA} <h1>What do you want to share?</h1> <DataSelector /> {:else} @@ -19,14 +19,14 @@ You are <br /> sharing <b>a text.</b> </h1> - {#if $connection === ConnectionState.CONNECTED} + {#if $state === ShareStateType.CONNECTING} + <p>Connecting to signaling server...</p> + {:else} <p> Your share is available under: <br /> - rook.rnrd.eu/<span>{getOwnToken()}</span> + rook.rnrd.eu/<span>{token()}</span> </p> <DataView /> - {:else} - <p>Connecting to signaling server...</p> {/if} {/if} diff --git a/assets/src/entries/request.ts b/assets/src/entries/request.ts index 4b13ca7..d8b9b93 100644 --- a/assets/src/entries/request.ts +++ b/assets/src/entries/request.ts @@ -1,8 +1,10 @@ import RequestPage from "../components/RequestPage.svelte"; import { RookType } from "../models/rook_type"; import { setClientType } from "../state/constant_state"; +import { initializeRequest } from "../state/request"; setClientType(RookType.REQUEST); +initializeRequest(); const app = new RequestPage({ target: document.getElementById("app"), diff --git a/assets/src/entries/share.ts b/assets/src/entries/share.ts index f38ce98..f7890b0 100644 --- a/assets/src/entries/share.ts +++ b/assets/src/entries/share.ts @@ -1,8 +1,10 @@ import SharePage from "../components/SharePage.svelte"; import { RookType } from "../models/rook_type"; import { setClientType } from "../state/constant_state"; +import { initializeShare } from "../state/share"; setClientType(RookType.SHARE); +initializeShare(); const app = new SharePage({ target: document.getElementById("app"), diff --git a/assets/src/models/incoming_request.ts b/assets/src/models/incoming_request.ts index 4af1e02..f784540 100644 --- a/assets/src/models/incoming_request.ts +++ b/assets/src/models/incoming_request.ts @@ -1,6 +1,5 @@ -import { bindTransfer, Transfer } from "../network/transfer/transfer"; +import type { 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 { @@ -58,16 +57,3 @@ export function newIncomingRequest( 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 deleted file mode 100644 index 2ad29af..0000000 --- a/assets/src/models/own_request.ts +++ /dev/null @@ -1,40 +0,0 @@ -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/transfer/request_transfer.ts b/assets/src/network/transfer/request_transfer.ts index e69a9a8..6e4b322 100644 --- a/assets/src/network/transfer/request_transfer.ts +++ b/assets/src/network/transfer/request_transfer.ts @@ -1,17 +1,13 @@ -import data from "../../stores/data"; -import { on, send } from "../channel/connection"; -import type { ShareIceCandidateMessage } from "../channel/messages/messages"; -import { - createTransfer, - onIncomingIceCandidate, - Transfer, - unregisterIceOnComplete, -} from "./transfer"; - -export async function createAnswerTransfer( - offer: RTCSessionDescriptionInit +import data from "../../state/data"; +import type { Connection } from "../channel/connection"; +import { createTransfer, Transfer } from "./transfer"; + +export async function respondToOffer( + c: Connection, + offer: RTCSessionDescriptionInit, + onComplete: () => void ): Promise<Transfer> { - const transfer = createTransfer(onChannel); + const transfer = createTransfer(c => onChannel(c, onComplete)); const offerDescription = new RTCSessionDescription(offer); transfer.pc.setRemoteDescription(offerDescription); @@ -22,19 +18,12 @@ export async function createAnswerTransfer( transfer.pc.onicecandidate = event => { const candidate = event.candidate; if (event.candidate !== null) { - send("ice_candidate", { candidate }); + // TODO: Check whether transfer was cancelled + c.send("ice_candidate", { candidate }); } }; - const unregisterIce = on( - "share_ice_candidate", - (message: ShareIceCandidateMessage) => - onIncomingIceCandidate(transfer, message) - ); - - unregisterIceOnComplete(transfer, unregisterIce); - - send("accept_share", { + c.send("accept_share", { sdp: answer.sdp, type: answer.type, }); diff --git a/assets/src/network/transfer/share_transfer.ts b/assets/src/network/transfer/share_transfer.ts index f641a56..85e67b0 100644 --- a/assets/src/network/transfer/share_transfer.ts +++ b/assets/src/network/transfer/share_transfer.ts @@ -1,22 +1,18 @@ import { get } from "svelte/store"; -import dataStore from "../../stores/data"; -import { onWithToken, send } from "../channel/connection"; -import type { UnregisterFn } from "../channel/messages/event_handler"; +import dataStore from "../../state/data"; +import type { Connection } from "../channel/connection"; import type { RequestIceCandidateMessage, ShareAcceptedMessage, } from "../channel/messages/messages"; -import { - createTransfer, - onIncomingIceCandidate, - Transfer, - unregisterIceOnComplete, -} from "./transfer"; +import { createTransfer, addRemoteIceCandidate, Transfer, TransferState } from "./transfer"; -export async function createOfferTransfer( - request_token: string +export async function createTranferAndSendOffer( + c: Connection, + request_token: string, + onComplete: () => void ): Promise<Transfer> { - const transfer = createTransfer(onChannel); + const transfer = createTransfer(c => onChannel(c, onComplete)); const offer = await transfer.pc.createOffer(); transfer.pc.setLocalDescription(offer); @@ -24,46 +20,26 @@ export async function createOfferTransfer( transfer.pc.onicecandidate = event => { const candidate = event.candidate; if (event.candidate !== null) { - send("ice_candidate", { candidate, token: request_token }); + // TODO: Check whether transfer was cancelled and don't send if so. + c.send("ice_candidate", { candidate, token: request_token }); } }; - send("accept_request", { + c.send("accept_request", { token: request_token, sdp: offer.sdp, type: offer.type, }); - const unregister: UnregisterFn = onWithToken( - "share_accepted", - request_token, - (message: ShareAcceptedMessage) => - onShareAccepted(transfer, message, unregister) - ); - return transfer; } -function onShareAccepted( +export function addRemoteDescription( transfer: Transfer, - message: ShareAcceptedMessage, - unregister: UnregisterFn + session: RTCSessionDescriptionInit ) { - const token = message.token; - - const answerDescription = new RTCSessionDescription(message); + const answerDescription = new RTCSessionDescription(session); transfer.pc.setRemoteDescription(answerDescription); - - const unregisterIce = onWithToken( - "request_ice_candidate", - token, - (message: RequestIceCandidateMessage) => - onIncomingIceCandidate(transfer, message) - ); - - unregisterIceOnComplete(transfer, unregisterIce); - - unregister(); } function onChannel(channel: RTCDataChannel, completeTransfer: () => void) { diff --git a/assets/src/network/transfer/transfer.ts b/assets/src/network/transfer/transfer.ts index e950589..399250f 100644 --- a/assets/src/network/transfer/transfer.ts +++ b/assets/src/network/transfer/transfer.ts @@ -1,11 +1,4 @@ import { Writable, writable } from "svelte/store"; -import type { IncomingRequest } from "../../models/incoming_request"; -import type { OwnRequest } from "../../models/own_request"; -import type { UnregisterFn } from "../channel/messages/event_handler"; -import type { - RequestIceCandidateMessage, - ShareIceCandidateMessage, -} from "../channel/messages/messages"; export enum TransferState { CONNECTING, @@ -49,6 +42,7 @@ export function createTransfer( }; channel.onopen = () => { + console.log("Transfer channel open."); state.set(TransferState.TRANSFERRING); onChannel(channel, () => onTransferComplete(transfer)); }; @@ -56,43 +50,11 @@ export function createTransfer( return transfer; } -export function bindTransfer( - request: OwnRequest | IncomingRequest, - transferPromise: Promise<Transfer>, - completeTransfer: () => 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; - - completeTransfer(); - } - }); - }); -} - -export function onIncomingIceCandidate( +export function addRemoteIceCandidate( transfer: Transfer, - message: ShareIceCandidateMessage | RequestIceCandidateMessage + candidate: RTCIceCandidateInit ) { - transfer.pc.addIceCandidate(message.candidate); -} - -export function unregisterIceOnComplete( - transfer: Transfer, - unregister: UnregisterFn -) { - transfer.pc.onicegatheringstatechange = event => { - const connection = event.target as any; - if (connection.iceGatheringState === "complete") { - unregister(); - } - }; + transfer.pc.addIceCandidate(candidate); } function onTransferComplete(transfer: Transfer) { diff --git a/assets/src/state/received_requests.ts b/assets/src/state/received_requests.ts deleted file mode 100644 index 48916ad..0000000 --- a/assets/src/state/received_requests.ts +++ /dev/null @@ -1,17 +0,0 @@ -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/state/request.ts b/assets/src/state/request.ts new file mode 100644 index 0000000..30e71af --- /dev/null +++ b/assets/src/state/request.ts @@ -0,0 +1,153 @@ +import { writable, Writable } from "svelte/store"; +import { RookType } from "../models/rook_type"; +import { Connection } from "../network/channel/connection"; +import type { + RequestAcceptedMessage, + RequestAcknowledgedMessage, + ShareCancelledMessage, + ShareIceCandidateMessage, +} from "../network/channel/messages/messages"; +import { respondToOffer } from "../network/transfer/request_transfer"; +import { addRemoteIceCandidate, Transfer } from "../network/transfer/transfer"; +import { isClientRequest } from "./constant_state"; +import b from "../utils/bind"; + +export enum RequestStateType { + CONNECTING, + + WAITING_FOR_RESPONSE, + + IN_FLIGHT, + DONE, + + DECLINED, + SHARE_CANCELLED, + NO_SUCH_SHARE, +} + +type RequestState = { + type: Writable<RequestStateType>; + state: + | Connecting + | WaitingForResponse + | Transferring + | Done + | Declined + | ShareCancelled + | NoSuchShare; +}; + +let request: RequestState | null = null; + +export function initializeRequest() { + if (!isClientRequest()) { + throw new Error( + "Tried to initialize request state on non-request client." + ); + } + + if (request) { + throw new Error("Request state already initialized."); + } + + request = { + type: writable(RequestStateType.CONNECTING), + state: new Connecting(), + }; +} + +export function getRequestState(): RequestState { + if (!isClientRequest()) { + throw new Error("Tried to access share state on non-share client."); + } + + return request; +} + +class Connecting { + private connection: Connection; + + constructor() { + this.connection = new Connection(); + this.connection.setChannelMessageHandler({ + request_acknowledged: b(this, this.onRequestAcknowledged), + }); + + this.connection.start(RookType.REQUEST); + } + + private onRequestAcknowledged(m: RequestAcknowledgedMessage) { + request.type.set(RequestStateType.WAITING_FOR_RESPONSE); + request.state = new WaitingForResponse(this.connection); + } +} + +class WaitingForResponse { + private connection: Connection; + + constructor(connection: Connection) { + this.connection = connection; + + this.connection.setChannelMessageHandler({ + request_accepted: b(this, this.onRequestAccepted), + share_cancelled: b(this, this.onShareCancelled), + // TODO: request_declined + }); + } + + private onRequestAccepted(m: RequestAcceptedMessage) { + request.type.set(RequestStateType.IN_FLIGHT); + request.state = new Transferring(this.connection, m); + } + + private onShareCancelled(m: ShareCancelledMessage) { + request.type.set(RequestStateType.SHARE_CANCELLED); + request.state = null; + } +} + +class Transferring { + private connection: Connection; + private transfer: Transfer | null = null; + private unaddedIceCandidates: RTCIceCandidateInit[] = []; + + constructor(connection: Connection, offer: RTCSessionDescriptionInit) { + this.connection = connection; + this.connection.setChannelMessageHandler({ + share_ice_candidate: b(this, this.onShareIceCandidate), + // TODO: share_cancelled + }); + + const offerPromise = respondToOffer( + this.connection, + offer, + b(this, this.onTransferComplete) + ); + + offerPromise.then(transfer => { + for (const candidate of this.unaddedIceCandidates) { + addRemoteIceCandidate(transfer, candidate); + } + this.unaddedIceCandidates = []; + }); + } + + private onShareIceCandidate(m: ShareIceCandidateMessage) { + if (!this.transfer) { + this.unaddedIceCandidates.push(m.candidate); + } else { + addRemoteIceCandidate(this.transfer, m.candidate); + } + } + + private onTransferComplete() { + request.type.set(RequestStateType.DONE); + request.state = null; + } +} + +// Finished states. +type Done = null; +type Declined = null; +type ShareCancelled = null; +type NoSuchShare = null; diff --git a/assets/src/state/share.ts b/assets/src/state/share.ts new file mode 100644 index 0000000..5a2aaae --- /dev/null +++ b/assets/src/state/share.ts @@ -0,0 +1,172 @@ +import { get, Readable, writable, Writable } from "svelte/store"; +import { + IncomingRequest, + IncomingRequestState, + newIncomingRequest, +} from "../models/incoming_request"; +import { RookType } from "../models/rook_type"; +import { Connection } from "../network/channel/connection"; +import type { + NewRequestMessage, + RequestCancelledMessage, + RequestIceCandidateMessage, + ShareAcceptedMessage, +} from "../network/channel/messages/messages"; +import { + addRemoteDescription, + createTranferAndSendOffer, +} from "../network/transfer/share_transfer"; +import { addRemoteIceCandidate } from "../network/transfer/transfer"; +import { isClientShare } from "./constant_state"; +import data from "./data"; +import b from "../utils/bind"; + +export enum ShareStateType { + CHOOSING_DATA, + CONNECTING, + SHARING, +} + +type ShareState = { + type: Writable<ShareStateType>; + state: ChoosingData | Connecting | Sharing; +}; + +let share: ShareState | null = null; + +export function initializeShare() { + if (!isClientShare()) { + throw new Error("Tried to initialize share state on non-share client."); + } + + if (share) { + throw new Error("Share state already initialized."); + } + + share = { + type: writable(ShareStateType.CHOOSING_DATA), + state: new ChoosingData(), + }; +} + +export function getShareState(): ShareState { + if (!isClientShare()) { + throw new Error("Tried to access share state on non-share client."); + } + + return share; +} + +export class ChoosingData { + public submitData(d: string): void { + data.set(d); + + share.type.set(ShareStateType.CONNECTING); + share.state = new Connecting(); + } +} + +export class Connecting { + private connection: Connection; + + constructor() { + this.connection = new Connection(); + this.connection.setChannelMessageHandler({}); + + this.connection.start(RookType.SHARE).then(b(this, this.onConnect)); + } + + private onConnect() { + share.type.set(ShareStateType.SHARING); + share.state = new Sharing(this.connection); + } +} + +export class Sharing { + private incomingRequests: Writable<{ [key: string]: IncomingRequest }> = + writable({}); + + private connection: Connection; + + constructor(connection: Connection) { + this.connection = connection; + this.connection.setChannelMessageHandler({ + new_request: b(this, this.onNewRequest), + request_cancelled: b(this, this.onRequestCancelled), + + share_accepted: b(this, this.onShareAccepted), + request_ice_candidate: b(this, this.onRequestIceCandidate), + }); + } + + public getToken(): string { + return this.connection.token; + } + + public getRequests(): Readable<{ [key: string]: IncomingRequest }> { + return this.incomingRequests; + } + + public async acceptRequest(request: IncomingRequest) { + request.state.set(IncomingRequestState.IN_FLIGHT); + + const transfer = await createTranferAndSendOffer( + this.connection, + request.info.token, + () => { + this.onRequestTransferComplete(request); + } + ); + request.transfer = transfer; + } + + public async declineRequest(request: IncomingRequest) { + // TODO: Implement. + throw new Error("Declining requests is not implemented yet."); + } + + private onNewRequest(m: NewRequestMessage) { + const request = newIncomingRequest(m.token, m.ip, m.location, m.client); + + const mapping = { + [m.token]: request, + }; + + // TODO: Check if the request is already in the list. + + this.incomingRequests.update(requests => { + return { + ...requests, + ...mapping, + }; + }); + } + + private onRequestCancelled(m: RequestCancelledMessage) { + // TODO: Cancel ongoing share. + + this.incomingRequests.update(requests => { + const newRequests = { + ...requests, + }; + // TODO: Check if the request is in the list. + delete newRequests[m.token]; + return newRequests; + }); + } + + private onShareAccepted(m: ShareAcceptedMessage) { + // TODO: Check if the request is in the list. + const request = get(this.incomingRequests)[m.token]; + addRemoteDescription(request.transfer, m); + } + + private onRequestIceCandidate(m: RequestIceCandidateMessage) { + const request = get(this.incomingRequests)[m.token]; + addRemoteIceCandidate(request.transfer, m.candidate); + } + + private onRequestTransferComplete(request: IncomingRequest) { + request.state.set(IncomingRequestState.DONE); + } +} diff --git a/assets/src/utils/bind.ts b/assets/src/utils/bind.ts new file mode 100644 index 0000000..fe2d8fd --- /dev/null +++ b/assets/src/utils/bind.ts @@ -0,0 +1,3 @@ +export default function <F extends Function>(to: object, f: F): F { + return f.bind(to); +} |
