aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-02-19 20:00:38 +0100
committerMel <einebeere@gmail.com>2022-02-19 20:00:38 +0100
commitbf1450799df0deb424a9675be89e13c29e3620d7 (patch)
tree6a5f2f7559c5946058deadf8375f6609485a3d3f
parent5384c34952b031995ecb8aa58d72954b0c685e18 (diff)
downloadrook-bf1450799df0deb424a9675be89e13c29e3620d7.tar.zst
rook-bf1450799df0deb424a9675be89e13c29e3620d7.zip
Split state into stages to handle messages
-rw-r--r--assets/src/components/SharePage.svelte6
-rw-r--r--assets/src/components/request/RequestStatus.svelte21
-rw-r--r--assets/src/components/share/DataSelector.svelte8
-rw-r--r--assets/src/components/share/Request.svelte13
-rw-r--r--assets/src/components/share/RequestList.svelte16
-rw-r--r--assets/src/components/share/ShareStatus.svelte26
-rw-r--r--assets/src/entries/request.ts2
-rw-r--r--assets/src/entries/share.ts2
-rw-r--r--assets/src/models/incoming_request.ts16
-rw-r--r--assets/src/models/own_request.ts40
-rw-r--r--assets/src/network/transfer/request_transfer.ts33
-rw-r--r--assets/src/network/transfer/share_transfer.ts52
-rw-r--r--assets/src/network/transfer/transfer.ts46
-rw-r--r--assets/src/state/received_requests.ts17
-rw-r--r--assets/src/state/request.ts153
-rw-r--r--assets/src/state/share.ts172
-rw-r--r--assets/src/utils/bind.ts3
17 files changed, 410 insertions, 216 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";
+import data from "../../state/data";
+import type { Connection } from "../channel/connection";
+import { createTransfer, Transfer } from "./transfer";
-export async function createAnswerTransfer(
- offer: RTCSessionDescriptionInit
+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);
+}