aboutsummaryrefslogtreecommitdiff
path: root/assets/src/state
diff options
context:
space:
mode:
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);
+ }
+}