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/state | |
| parent | 5384c34952b031995ecb8aa58d72954b0c685e18 (diff) | |
| download | rook-bf1450799df0deb424a9675be89e13c29e3620d7.tar.zst rook-bf1450799df0deb424a9675be89e13c29e3620d7.zip | |
Split state into stages to handle messages
Diffstat (limited to 'assets/src/state')
| -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 |
3 files changed, 325 insertions, 17 deletions
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); + } +} |
