about summary refs log tree commit diff
path: root/assets/src/state
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 /assets/src/state
parent5384c34952b031995ecb8aa58d72954b0c685e18 (diff)
downloadrook-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.ts17
-rw-r--r--assets/src/state/request.ts153
-rw-r--r--assets/src/state/share.ts172
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);
+    }
+}