aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelonai <einebeere@gmail.com>2021-07-08 23:29:20 +0200
committerMelonai <einebeere@gmail.com>2021-07-08 23:29:20 +0200
commitc3b05a72b7185112ece6e42c99e9a828c8298f04 (patch)
tree317da6fbe640ffa57cf771b4b9e3b0beb80e836c
parent9d8ca2a653661560f471d717d188e92a79edb250 (diff)
downloadrook-c3b05a72b7185112ece6e42c99e9a828c8298f04.tar.zst
rook-c3b05a72b7185112ece6e42c99e9a828c8298f04.zip
State display for incoming and own requests
-rw-r--r--assets/src/components/RequestPage.svelte12
-rw-r--r--assets/src/components/SharePage.svelte8
-rw-r--r--assets/src/components/request/RequestStatus.svelte51
-rw-r--r--assets/src/components/share/DataSelector.svelte40
-rw-r--r--assets/src/components/share/Request.svelte43
-rw-r--r--assets/src/components/share/RequestList.svelte (renamed from assets/src/components/share/Requests.svelte)6
-rw-r--r--assets/src/components/share/Selector.svelte20
-rw-r--r--assets/src/components/share/ShareStatus.svelte (renamed from assets/src/components/share/Info.svelte)4
-rw-r--r--assets/src/models/incoming_request.ts57
-rw-r--r--assets/src/models/own_request.ts40
-rw-r--r--assets/src/network/channel/request.ts24
-rw-r--r--assets/src/network/channel/request_connection.ts48
-rw-r--r--assets/src/network/channel/share_connection.ts (renamed from assets/src/network/channel/share.ts)14
-rw-r--r--assets/src/network/transfer/request_transfer.ts (renamed from assets/src/network/transfer/request.ts)14
-rw-r--r--assets/src/network/transfer/share_transfer.ts (renamed from assets/src/network/transfer/share.ts)11
-rw-r--r--assets/src/network/transfer/transfer.ts44
-rw-r--r--assets/src/stores/received_requests.ts17
-rw-r--r--assets/src/stores/requests.ts14
18 files changed, 356 insertions, 111 deletions
diff --git a/assets/src/components/RequestPage.svelte b/assets/src/components/RequestPage.svelte
index eb6b559..d8cf31d 100644
--- a/assets/src/components/RequestPage.svelte
+++ b/assets/src/components/RequestPage.svelte
@@ -1,21 +1,13 @@
<script lang="ts">
- import { start } from "../network/channel/connection";
- import { startRequest } from "../network/channel/request";
-
import Header from "./Header.svelte";
-
- start().then(startRequest);
+ import RequestStatus from "./request/RequestStatus.svelte";
</script>
<Header color="white" />
<main>
<div class="left-segment">
- <h1>Waiting for a response...</h1>
- <p>
- The share’s content will become available to you once the sharer
- decides to accept your request.
- </p>
+ <RequestStatus />
</div>
<div class="right-segment" />
</main>
diff --git a/assets/src/components/SharePage.svelte b/assets/src/components/SharePage.svelte
index 9be9466..19138ef 100644
--- a/assets/src/components/SharePage.svelte
+++ b/assets/src/components/SharePage.svelte
@@ -1,20 +1,20 @@
<script lang="ts">
import data from "../stores/data";
import Header from "./Header.svelte";
- import Info from "./share/Info.svelte";
- import Requests from "./share/Requests.svelte";
+ import ShareStatus from "./share/ShareStatus.svelte";
+ import RequestList from "./share/RequestList.svelte";
</script>
<Header color="black" />
<main>
<div class="left-segment">
- <Info />
+ <ShareStatus />
</div>
<div class="right-segment">
{#if $data.locked}
<h1>Requests</h1>
- <Requests />
+ <RequestList />
{/if}
</div>
</main>
diff --git a/assets/src/components/request/RequestStatus.svelte b/assets/src/components/request/RequestStatus.svelte
new file mode 100644
index 0000000..b4c9ef9
--- /dev/null
+++ b/assets/src/components/request/RequestStatus.svelte
@@ -0,0 +1,51 @@
+<script lang="ts">
+ import data from "../../stores/data";
+ import {
+ initializeRequest,
+ OwnRequestState,
+ } from "../../models/own_request";
+ import { startRequestConnection } from "../../network/channel/request_connection";
+
+ const request = initializeRequest();
+ const state = request.state;
+
+ startRequestConnection(request);
+</script>
+
+<!-- TODO: Bind states of same path together -->
+{#if $state === OwnRequestState.PENDING || $state === OwnRequestState.ACKNOWLEDGED}
+ <h1>Waiting for a response...</h1>
+ <p>
+ {#if $state === OwnRequestState.ACKNOWLEDGED}
+ 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}
+ <h1>Your request was <b>accepted!</b></h1>
+ {#if $state === OwnRequestState.IN_FLIGHT}
+ Transferring...
+ {:else}
+ <p>Congratulations! You can access the received data below:</p>
+ <div class="data">{$data.data}</div>
+ {/if}
+{:else if $state === OwnRequestState.DECLINED}
+ <h1>Your request was <b>declined!</b></h1>
+ <p>Sorry! I hope we can still be friends?</p>
+{:else}
+ <!-- TODO: Handle specific errors -->
+ <h1>Eek!</h1>
+ <p>An error occured during your request.</p>
+{/if}
+
+<style>
+ .data {
+ font-size: 14px;
+ width: 100%;
+ border: solid 1px #cccccc;
+ padding: 10px 20px;
+ box-sizing: border-box;
+ }
+</style>
diff --git a/assets/src/components/share/DataSelector.svelte b/assets/src/components/share/DataSelector.svelte
new file mode 100644
index 0000000..6838a05
--- /dev/null
+++ b/assets/src/components/share/DataSelector.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+ import { startShareConnection } from "../../network/channel/share_connection";
+
+ import data from "../../stores/data";
+
+ let value = "";
+
+ const submit = () => {
+ data.set(value);
+ startShareConnection();
+ };
+
+ // TODO: Accept data other than text.
+</script>
+
+<form on:submit|preventDefault={submit}>
+ <!-- TODO: Prettier input field -->
+ <input type="text" bind:value />
+ <input class="set-data-button" type="submit" value="Share" />
+</form>
+
+<style>
+ form {
+ display: flex;
+ flex-wrap: nowrap;
+ }
+
+ input {
+ border: none;
+ font-size: 14px;
+ color: black;
+ background-color: white;
+ padding: 10px 20px;
+ box-sizing: border-box;
+ }
+
+ .set-data-button {
+ margin-left: 0.5rem;
+ }
+</style>
diff --git a/assets/src/components/share/Request.svelte b/assets/src/components/share/Request.svelte
index e34f633..5f9ca7a 100644
--- a/assets/src/components/share/Request.svelte
+++ b/assets/src/components/share/Request.svelte
@@ -1,31 +1,50 @@
<script lang="ts">
- import { offer } from "../../network/transfer/share";
+ import {
+ acceptIncomingRequest,
+ declineIncomingRequest,
+ IncomingRequestState,
+ } from "../../models/incoming_request";
+ import type { IncomingRequest } from "../../models/incoming_request";
import CheckIcon from "../icons/CheckIcon.svelte";
import CloseIcon from "../icons/CloseIcon.svelte";
- export let token: string;
+ export let request: IncomingRequest;
+ const state = request.state;
async function accept() {
- const transfer = await offer(token);
+ acceptIncomingRequest(request);
}
- function decline() {}
+ function decline() {
+ declineIncomingRequest(request);
+ }
</script>
<!-- TODO: Replace placeholder values and show IP instead of token. -->
<ul class="request">
- <div class="buttons">
- <div class="round-button" on:click={accept}>
- <CheckIcon color="white" />
- </div>
- <div class="plain-button" on:click={decline}>
- <CloseIcon color="black" />
+ {#if $state === IncomingRequestState.WAITING}
+ <div class="buttons">
+ <div class="round-button" on:click={accept}>
+ <CheckIcon color="white" />
+ </div>
+ <div class="plain-button" on:click={decline}>
+ <CloseIcon color="black" />
+ </div>
</div>
- </div>
+ {/if}
+
<li>Requested at 14:38</li>
- <li class="ip">{token}</li>
+ <li class="ip">{request.info.token}</li>
<li>Trusowo, Russia</li>
<li>Firefox 89</li>
+
+ {#if $state === IncomingRequestState.IN_FLIGHT}
+ Transferring...
+ {:else if $state === IncomingRequestState.DONE}
+ Done!
+ {:else if $state === IncomingRequestState.DECLINED}
+ Declined.
+ {/if}
</ul>
<style>
diff --git a/assets/src/components/share/Requests.svelte b/assets/src/components/share/RequestList.svelte
index 54eab57..423662e 100644
--- a/assets/src/components/share/Requests.svelte
+++ b/assets/src/components/share/RequestList.svelte
@@ -1,8 +1,8 @@
<script lang="ts">
- import requests from "../../stores/requests";
+ import requests from "../../stores/received_requests";
import Request from "./Request.svelte";
</script>
{#each $requests as request}
- <Request token={request} />
-{/each} \ No newline at end of file
+ <Request {request} />
+{/each}
diff --git a/assets/src/components/share/Selector.svelte b/assets/src/components/share/Selector.svelte
deleted file mode 100644
index e4c7c07..0000000
--- a/assets/src/components/share/Selector.svelte
+++ /dev/null
@@ -1,20 +0,0 @@
-<script lang="ts">
- import { start } from "../../network/channel/connection";
- import { startShare } from "../../network/channel/share";
-
- import data from "../../stores/data";
-
- let value = "";
-
- const submit = () => {
- data.set(value);
- start().then(startShare);
- };
-
- // TODO: Accept data other than text.
-</script>
-
-<form on:submit|preventDefault={submit}>
- <input type="text" bind:value />
- <input type="submit" value="Submit" />
-</form>
diff --git a/assets/src/components/share/Info.svelte b/assets/src/components/share/ShareStatus.svelte
index 79ef404..15d1ee9 100644
--- a/assets/src/components/share/Info.svelte
+++ b/assets/src/components/share/ShareStatus.svelte
@@ -5,14 +5,14 @@
getStateStore,
} from "../../network/channel/connection";
import data from "../../stores/data";
- import Selector from "./Selector.svelte";
+ import DataSelector from "./DataSelector.svelte";
let connection = getStateStore();
</script>
{#if !$data.locked}
<h1>What do you want to share?</h1>
- <Selector />
+ <DataSelector />
{:else}
<h1>
You are <br />
diff --git a/assets/src/models/incoming_request.ts b/assets/src/models/incoming_request.ts
new file mode 100644
index 0000000..e1eefb1
--- /dev/null
+++ b/assets/src/models/incoming_request.ts
@@ -0,0 +1,57 @@
+import { bindTransfer, 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 {
+ // Request was neither accepted nor declined yet
+ WAITING,
+ // Data is being transferred, more precise data
+ // is available in the corresponding Transfer object
+ IN_FLIGHT,
+ // Requestor has received all data
+ DONE,
+ // Request was declined
+ DECLINED,
+}
+
+// Identifying information about the requestor
+export type IncomingRequestInfo = {
+ token: string;
+};
+
+// The model for a request received by a sharer
+// The state marks changes in the progression of the request lifecycle and can be subscribed to
+export type IncomingRequest = {
+ // Transfer is null while request isn't IN_FLIGHT
+ transfer: Transfer | null;
+ info: IncomingRequestInfo;
+ state: Writable<IncomingRequestState>;
+};
+
+// Create a model for a new incoming request
+export function newIncomingRequest(token: string): IncomingRequest {
+ const info = {
+ token,
+ };
+
+ return {
+ transfer: null,
+ info,
+ // Each request starts out as just received and waiting for an answer
+ 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
new file mode 100644
index 0000000..2ad29af
--- /dev/null
+++ b/assets/src/models/own_request.ts
@@ -0,0 +1,40 @@
+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/channel/request.ts b/assets/src/network/channel/request.ts
deleted file mode 100644
index 693e408..0000000
--- a/assets/src/network/channel/request.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import getShareToken from "../../utils/getShareToken";
-import { answer } from "../transfer/request";
-import { Connection, ConnectionState, on, updateState } from "./connection";
-import type { RequestAcceptedMessage } from "./messages/messages";
-import { joinRequestChannel } from "./socket";
-
-export async function startRequest(connection: Connection) {
- updateState(ConnectionState.CONNECTING_CHANNEL);
-
- const requestChannel = await joinRequestChannel(
- connection.socket,
- connection.token,
- getShareToken()
- );
- connection.channel = requestChannel;
-
- on("request_accepted", onRequestAccepted);
-
- updateState(ConnectionState.CONNECTED);
-}
-
-async function onRequestAccepted(message: RequestAcceptedMessage) {
- await answer(message);
-}
diff --git a/assets/src/network/channel/request_connection.ts b/assets/src/network/channel/request_connection.ts
new file mode 100644
index 0000000..1b873d3
--- /dev/null
+++ b/assets/src/network/channel/request_connection.ts
@@ -0,0 +1,48 @@
+import {
+ requestAccepted,
+ OwnRequest,
+ OwnRequestState,
+} from "../../models/own_request";
+import getShareToken from "../../utils/getShareToken";
+import { ConnectionState, on, start, updateState } from "./connection";
+import type {
+ RequestAcceptedMessage,
+ ShareCancelledMessage,
+} from "./messages/messages";
+import { joinRequestChannel } from "./socket";
+
+export async function startRequestConnection(ownRequest: OwnRequest) {
+ const connection = await start();
+
+ updateState(ConnectionState.CONNECTING_CHANNEL);
+
+ const requestChannel = await joinRequestChannel(
+ connection.socket,
+ connection.token,
+ getShareToken()
+ );
+ connection.channel = requestChannel;
+
+ on("request_accepted", (message: RequestAcceptedMessage) =>
+ onRequestAccepted(message, ownRequest)
+ );
+
+ on("share_cancelled", (message: ShareCancelledMessage) =>
+ onShareCancelled(message, ownRequest)
+ );
+
+ updateState(ConnectionState.CONNECTED);
+}
+
+// Events which can happen without prior triggers during a request's lifetime
+
+function onRequestAccepted(
+ message: RequestAcceptedMessage,
+ request: OwnRequest
+) {
+ requestAccepted(request, message);
+}
+
+function onShareCancelled(message: ShareCancelledMessage, request: OwnRequest) {
+ request.state.set(OwnRequestState.SHARE_CANCELLED);
+} \ No newline at end of file
diff --git a/assets/src/network/channel/share.ts b/assets/src/network/channel/share_connection.ts
index 81942af..415cb7f 100644
--- a/assets/src/network/channel/share.ts
+++ b/assets/src/network/channel/share_connection.ts
@@ -1,9 +1,10 @@
-import requests from "../../stores/requests";
+import { newIncomingRequest } from "../../models/incoming_request";
+import requests from "../../stores/received_requests";
import {
- Connection,
ConnectionState,
on,
onWithToken,
+ start,
updateState,
} from "./connection";
import type { UnregisterHandler } from "./messages/handler";
@@ -13,7 +14,9 @@ import type {
} from "./messages/messages";
import { joinShareChannel } from "./socket";
-export async function startShare(connection: Connection) {
+export async function startShareConnection() {
+ const connection = await start();
+
updateState(ConnectionState.CONNECTING_CHANNEL);
const shareChannel = await joinShareChannel(
@@ -27,10 +30,13 @@ export async function startShare(connection: Connection) {
updateState(ConnectionState.CONNECTED);
}
+// Events which can happen without prior triggers during a share's lifetime
+
function onNewRequest(message: NewRequestMessage) {
const token = message.token;
- requests.addRequest(token);
+ const request = newIncomingRequest(token);
+ requests.addRequest(request);
onWithToken("request_cancelled", token, onRequestCancelled);
}
diff --git a/assets/src/network/transfer/request.ts b/assets/src/network/transfer/request_transfer.ts
index 920cd9a..317f5e5 100644
--- a/assets/src/network/transfer/request.ts
+++ b/assets/src/network/transfer/request_transfer.ts
@@ -1,17 +1,17 @@
+import data from "../../stores/data";
import { on, send } from "../channel/connection";
import type { RequestIceCandidateMessage } from "../channel/messages/messages";
import {
createTransfer,
onIncomingIceCandidate,
Transfer,
- TransferType,
unregisterIceOnComplete,
} from "./transfer";
-export async function answer(
+export async function createAnswerTransfer(
offer: RTCSessionDescriptionInit
): Promise<Transfer> {
- const transfer = createTransfer(TransferType.ANSWER, onChannel);
+ const transfer = createTransfer(onChannel);
const offerDescription = new RTCSessionDescription(offer);
transfer.pc.setRemoteDescription(offerDescription);
@@ -42,8 +42,10 @@ export async function answer(
return transfer;
}
-function onChannel(channel: RTCDataChannel) {
+function onChannel(channel: RTCDataChannel, completeTransfer: () => void) {
channel.onmessage = event => {
- console.log(event.data)
- }
+ data.set(event.data);
+ // TODO: Disconnect from channel
+ completeTransfer();
+ };
}
diff --git a/assets/src/network/transfer/share.ts b/assets/src/network/transfer/share_transfer.ts
index 5e43df0..fc6df9c 100644
--- a/assets/src/network/transfer/share.ts
+++ b/assets/src/network/transfer/share_transfer.ts
@@ -10,12 +10,13 @@ import {
createTransfer,
onIncomingIceCandidate,
Transfer,
- TransferType,
unregisterIceOnComplete,
} from "./transfer";
-export async function offer(request_token: string): Promise<Transfer> {
- const transfer = createTransfer(TransferType.OFFER, onChannel);
+export async function createOfferTransfer(
+ request_token: string
+): Promise<Transfer> {
+ const transfer = createTransfer(onChannel);
const offer = await transfer.pc.createOffer();
transfer.pc.setLocalDescription(offer);
@@ -65,7 +66,9 @@ function onShareAccepted(
unregister();
}
-function onChannel(channel: RTCDataChannel) {
+function onChannel(channel: RTCDataChannel, completeTransfer: () => void) {
const data = get(dataStore).data;
channel.send(data);
+ // TODO: Add retransmission possibility in case of transfer failure?
+ completeTransfer();
}
diff --git a/assets/src/network/transfer/transfer.ts b/assets/src/network/transfer/transfer.ts
index 976d113..7641c17 100644
--- a/assets/src/network/transfer/transfer.ts
+++ b/assets/src/network/transfer/transfer.ts
@@ -1,18 +1,22 @@
+import { Writable, writable } from "svelte/store";
+import type { IncomingRequest } from "../../models/incoming_request";
+import type { OwnRequest } from "../../models/own_request";
import type { UnregisterHandler } from "../channel/messages/handler";
import type {
RequestIceCandidateMessage,
ShareIceCandidateMessage,
} from "../channel/messages/messages";
-export enum TransferType {
- OFFER,
- ANSWER,
+export enum TransferState {
+ CONNECTING,
+ TRANSFERRING,
+ DONE,
}
export type Transfer = {
pc: RTCPeerConnection;
channel: RTCDataChannel;
- type: TransferType;
+ state: Writable<TransferState>;
};
const servers = {
@@ -28,24 +32,48 @@ const servers = {
};
export function createTransfer(
- type: TransferType,
- onChannel: (channel: RTCDataChannel) => void
+ onChannel: (channel: RTCDataChannel, completeTransfer: () => void) => void
): Transfer {
const pc = new RTCPeerConnection(servers);
const channel = pc.createDataChannel("channel", {
negotiated: true,
id: 0,
});
+ const state = writable(TransferState.CONNECTING);
- channel.onopen = () => onChannel(channel);
+ channel.onopen = () => {
+ state.set(TransferState.TRANSFERRING);
+ const completeTransfer = () => state.set(TransferState.DONE);
+ onChannel(channel, completeTransfer);
+ };
return {
pc,
channel,
- type,
+ state,
};
}
+export function bindTransfer(
+ request: OwnRequest | IncomingRequest,
+ transferPromise: Promise<Transfer>,
+ onTransferComplete: () => 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;
+
+ onTransferComplete();
+ }
+ });
+ });
+}
+
export function onIncomingIceCandidate(
transfer: Transfer,
message: ShareIceCandidateMessage | RequestIceCandidateMessage
diff --git a/assets/src/stores/received_requests.ts b/assets/src/stores/received_requests.ts
new file mode 100644
index 0000000..48916ad
--- /dev/null
+++ b/assets/src/stores/received_requests.ts
@@ -0,0 +1,17 @@
+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/stores/requests.ts b/assets/src/stores/requests.ts
deleted file mode 100644
index 1dc8cb2..0000000
--- a/assets/src/stores/requests.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { writable } from "svelte/store";
-
-const createRequestStore = () => {
- const { subscribe, update } = writable<string[]>([]);
-
- return {
- subscribe,
- addRequest: request => update(state => [request, ...state]),
- removeRequest: request =>
- update(state => state.filter(r => r !== request)),
- };
-};
-
-export default createRequestStore();