From c3b05a72b7185112ece6e42c99e9a828c8298f04 Mon Sep 17 00:00:00 2001 From: Melonai Date: Thu, 8 Jul 2021 23:29:20 +0200 Subject: State display for incoming and own requests --- assets/src/components/RequestPage.svelte | 12 +--- assets/src/components/SharePage.svelte | 8 +-- assets/src/components/request/RequestStatus.svelte | 51 +++++++++++++++ assets/src/components/share/DataSelector.svelte | 40 ++++++++++++ assets/src/components/share/Info.svelte | 46 -------------- assets/src/components/share/Request.svelte | 43 +++++++++---- assets/src/components/share/RequestList.svelte | 8 +++ assets/src/components/share/Requests.svelte | 8 --- assets/src/components/share/Selector.svelte | 20 ------ assets/src/components/share/ShareStatus.svelte | 46 ++++++++++++++ assets/src/models/incoming_request.ts | 57 +++++++++++++++++ assets/src/models/own_request.ts | 40 ++++++++++++ assets/src/network/channel/request.ts | 24 ------- assets/src/network/channel/request_connection.ts | 48 ++++++++++++++ assets/src/network/channel/share.ts | 45 ------------- assets/src/network/channel/share_connection.ts | 51 +++++++++++++++ assets/src/network/transfer/request.ts | 49 -------------- assets/src/network/transfer/request_transfer.ts | 51 +++++++++++++++ assets/src/network/transfer/share.ts | 71 --------------------- assets/src/network/transfer/share_transfer.ts | 74 ++++++++++++++++++++++ assets/src/network/transfer/transfer.ts | 44 ++++++++++--- assets/src/stores/received_requests.ts | 17 +++++ assets/src/stores/requests.ts | 14 ---- 23 files changed, 556 insertions(+), 311 deletions(-) create mode 100644 assets/src/components/request/RequestStatus.svelte create mode 100644 assets/src/components/share/DataSelector.svelte delete mode 100644 assets/src/components/share/Info.svelte create mode 100644 assets/src/components/share/RequestList.svelte delete mode 100644 assets/src/components/share/Requests.svelte delete mode 100644 assets/src/components/share/Selector.svelte create mode 100644 assets/src/components/share/ShareStatus.svelte create mode 100644 assets/src/models/incoming_request.ts create mode 100644 assets/src/models/own_request.ts delete mode 100644 assets/src/network/channel/request.ts create mode 100644 assets/src/network/channel/request_connection.ts delete mode 100644 assets/src/network/channel/share.ts create mode 100644 assets/src/network/channel/share_connection.ts delete mode 100644 assets/src/network/transfer/request.ts create mode 100644 assets/src/network/transfer/request_transfer.ts delete mode 100644 assets/src/network/transfer/share.ts create mode 100644 assets/src/network/transfer/share_transfer.ts create mode 100644 assets/src/stores/received_requests.ts delete mode 100644 assets/src/stores/requests.ts (limited to 'assets') 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 @@
-

Waiting for a response...

-

- The share’s content will become available to you once the sharer - decides to accept your request. -

+
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 @@
- +
{#if $data.locked}

Requests

- + {/if}
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 @@ + + + +{#if $state === OwnRequestState.PENDING || $state === OwnRequestState.ACKNOWLEDGED} +

Waiting for a response...

+

+ {#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} +

+{:else if $state === OwnRequestState.IN_FLIGHT || $state === OwnRequestState.DONE} +

Your request was accepted!

+ {#if $state === OwnRequestState.IN_FLIGHT} + Transferring... + {:else} +

Congratulations! You can access the received data below:

+
{$data.data}
+ {/if} +{:else if $state === OwnRequestState.DECLINED} +

Your request was declined!

+

Sorry! I hope we can still be friends?

+{:else} + +

Eek!

+

An error occured during your request.

+{/if} + + 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 @@ + + +
+ + + +
+ + diff --git a/assets/src/components/share/Info.svelte b/assets/src/components/share/Info.svelte deleted file mode 100644 index 79ef404..0000000 --- a/assets/src/components/share/Info.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - -{#if !$data.locked} -

What do you want to share?

- -{:else} -

- You are
- sharing a text. -

- {#if $connection === ConnectionState.CONNECTED} -

- Your share is available under:
- rook.rnrd.eu/{getOwnToken()} -

- -
••••••••••••••••••••••••••••••
- {:else} -

Connecting to signaling server...

- {/if} -{/if} - - 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 @@
    -
    -
    - -
    -
    - + {#if $state === IncomingRequestState.WAITING} +
    +
    + +
    +
    + +
    -
    + {/if} +
  • Requested at 14:38
  • -
  • {token}
  • +
  • {request.info.token}
  • Trusowo, Russia
  • Firefox 89
  • + + {#if $state === IncomingRequestState.IN_FLIGHT} + Transferring... + {:else if $state === IncomingRequestState.DONE} + Done! + {:else if $state === IncomingRequestState.DECLINED} + Declined. + {/if}
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; +}; + +// 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; +}; + +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.ts deleted file mode 100644 index 81942af..0000000 --- a/assets/src/network/channel/share.ts +++ /dev/null @@ -1,45 +0,0 @@ -import requests from "../../stores/requests"; -import { - Connection, - ConnectionState, - on, - onWithToken, - updateState, -} from "./connection"; -import type { UnregisterHandler } from "./messages/handler"; -import type { - NewRequestMessage, - RequestCancelledMessage, -} from "./messages/messages"; -import { joinShareChannel } from "./socket"; - -export async function startShare(connection: Connection) { - updateState(ConnectionState.CONNECTING_CHANNEL); - - const shareChannel = await joinShareChannel( - connection.socket, - connection.token - ); - connection.channel = shareChannel; - - on("new_request", onNewRequest); - - updateState(ConnectionState.CONNECTED); -} - -function onNewRequest(message: NewRequestMessage) { - const token = message.token; - - requests.addRequest(token); - - onWithToken("request_cancelled", token, onRequestCancelled); -} - -function onRequestCancelled( - message: RequestCancelledMessage, - unregister: UnregisterHandler -) { - const token = message.token; - requests.removeRequest(token); - unregister(); -} diff --git a/assets/src/network/channel/share_connection.ts b/assets/src/network/channel/share_connection.ts new file mode 100644 index 0000000..415cb7f --- /dev/null +++ b/assets/src/network/channel/share_connection.ts @@ -0,0 +1,51 @@ +import { newIncomingRequest } from "../../models/incoming_request"; +import requests from "../../stores/received_requests"; +import { + ConnectionState, + on, + onWithToken, + start, + updateState, +} from "./connection"; +import type { UnregisterHandler } from "./messages/handler"; +import type { + NewRequestMessage, + RequestCancelledMessage, +} from "./messages/messages"; +import { joinShareChannel } from "./socket"; + +export async function startShareConnection() { + const connection = await start(); + + updateState(ConnectionState.CONNECTING_CHANNEL); + + const shareChannel = await joinShareChannel( + connection.socket, + connection.token + ); + connection.channel = shareChannel; + + on("new_request", onNewRequest); + + updateState(ConnectionState.CONNECTED); +} + +// Events which can happen without prior triggers during a share's lifetime + +function onNewRequest(message: NewRequestMessage) { + const token = message.token; + + const request = newIncomingRequest(token); + requests.addRequest(request); + + onWithToken("request_cancelled", token, onRequestCancelled); +} + +function onRequestCancelled( + message: RequestCancelledMessage, + unregister: UnregisterHandler +) { + const token = message.token; + requests.removeRequest(token); + unregister(); +} diff --git a/assets/src/network/transfer/request.ts b/assets/src/network/transfer/request.ts deleted file mode 100644 index 920cd9a..0000000 --- a/assets/src/network/transfer/request.ts +++ /dev/null @@ -1,49 +0,0 @@ -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( - offer: RTCSessionDescriptionInit -): Promise { - const transfer = createTransfer(TransferType.ANSWER, onChannel); - - const offerDescription = new RTCSessionDescription(offer); - transfer.pc.setRemoteDescription(offerDescription); - - const answer = await transfer.pc.createAnswer(); - transfer.pc.setLocalDescription(answer); - - transfer.pc.onicecandidate = event => { - const candidate = event.candidate; - if (event.candidate !== null) { - send("ice_candidate", { candidate }); - } - }; - - const unregisterIce = on( - "ice_candidate", - (message: RequestIceCandidateMessage) => - onIncomingIceCandidate(transfer, message) - ); - - unregisterIceOnComplete(transfer, unregisterIce); - - send("accept_share", { - sdp: answer.sdp, - type: answer.type, - }); - - return transfer; -} - -function onChannel(channel: RTCDataChannel) { - channel.onmessage = event => { - console.log(event.data) - } -} diff --git a/assets/src/network/transfer/request_transfer.ts b/assets/src/network/transfer/request_transfer.ts new file mode 100644 index 0000000..317f5e5 --- /dev/null +++ b/assets/src/network/transfer/request_transfer.ts @@ -0,0 +1,51 @@ +import data from "../../stores/data"; +import { on, send } from "../channel/connection"; +import type { RequestIceCandidateMessage } from "../channel/messages/messages"; +import { + createTransfer, + onIncomingIceCandidate, + Transfer, + unregisterIceOnComplete, +} from "./transfer"; + +export async function createAnswerTransfer( + offer: RTCSessionDescriptionInit +): Promise { + const transfer = createTransfer(onChannel); + + const offerDescription = new RTCSessionDescription(offer); + transfer.pc.setRemoteDescription(offerDescription); + + const answer = await transfer.pc.createAnswer(); + transfer.pc.setLocalDescription(answer); + + transfer.pc.onicecandidate = event => { + const candidate = event.candidate; + if (event.candidate !== null) { + send("ice_candidate", { candidate }); + } + }; + + const unregisterIce = on( + "ice_candidate", + (message: RequestIceCandidateMessage) => + onIncomingIceCandidate(transfer, message) + ); + + unregisterIceOnComplete(transfer, unregisterIce); + + send("accept_share", { + sdp: answer.sdp, + type: answer.type, + }); + + return transfer; +} + +function onChannel(channel: RTCDataChannel, completeTransfer: () => void) { + channel.onmessage = event => { + data.set(event.data); + // TODO: Disconnect from channel + completeTransfer(); + }; +} diff --git a/assets/src/network/transfer/share.ts b/assets/src/network/transfer/share.ts deleted file mode 100644 index 5e43df0..0000000 --- a/assets/src/network/transfer/share.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { get } from "svelte/store"; -import dataStore from "../../stores/data"; -import { onWithToken, send } from "../channel/connection"; -import type { UnregisterHandler } from "../channel/messages/handler"; -import type { - RequestIceCandidateMessage, - ShareAcceptedMessage, -} from "../channel/messages/messages"; -import { - createTransfer, - onIncomingIceCandidate, - Transfer, - TransferType, - unregisterIceOnComplete, -} from "./transfer"; - -export async function offer(request_token: string): Promise { - const transfer = createTransfer(TransferType.OFFER, onChannel); - - const offer = await transfer.pc.createOffer(); - transfer.pc.setLocalDescription(offer); - - transfer.pc.onicecandidate = event => { - const candidate = event.candidate; - if (event.candidate !== null) { - send("ice_candidate", { candidate, token: request_token }); - } - }; - - send("accept_request", { - token: request_token, - sdp: offer.sdp, - type: offer.type, - }); - - onWithToken( - "share_accepted", - request_token, - (message: ShareAcceptedMessage, unregister) => - onShareAccepted(transfer, message, unregister) - ); - - return transfer; -} - -function onShareAccepted( - transfer: Transfer, - message: ShareAcceptedMessage, - unregister: UnregisterHandler -) { - const token = message.token; - - const answerDescription = new RTCSessionDescription(message); - transfer.pc.setRemoteDescription(answerDescription); - - const unregisterIce = onWithToken( - "ice_candidate", - token, - (message: RequestIceCandidateMessage) => - onIncomingIceCandidate(transfer, message) - ); - - unregisterIceOnComplete(transfer, unregisterIce); - - unregister(); -} - -function onChannel(channel: RTCDataChannel) { - const data = get(dataStore).data; - channel.send(data); -} diff --git a/assets/src/network/transfer/share_transfer.ts b/assets/src/network/transfer/share_transfer.ts new file mode 100644 index 0000000..fc6df9c --- /dev/null +++ b/assets/src/network/transfer/share_transfer.ts @@ -0,0 +1,74 @@ +import { get } from "svelte/store"; +import dataStore from "../../stores/data"; +import { onWithToken, send } from "../channel/connection"; +import type { UnregisterHandler } from "../channel/messages/handler"; +import type { + RequestIceCandidateMessage, + ShareAcceptedMessage, +} from "../channel/messages/messages"; +import { + createTransfer, + onIncomingIceCandidate, + Transfer, + unregisterIceOnComplete, +} from "./transfer"; + +export async function createOfferTransfer( + request_token: string +): Promise { + const transfer = createTransfer(onChannel); + + const offer = await transfer.pc.createOffer(); + transfer.pc.setLocalDescription(offer); + + transfer.pc.onicecandidate = event => { + const candidate = event.candidate; + if (event.candidate !== null) { + send("ice_candidate", { candidate, token: request_token }); + } + }; + + send("accept_request", { + token: request_token, + sdp: offer.sdp, + type: offer.type, + }); + + onWithToken( + "share_accepted", + request_token, + (message: ShareAcceptedMessage, unregister) => + onShareAccepted(transfer, message, unregister) + ); + + return transfer; +} + +function onShareAccepted( + transfer: Transfer, + message: ShareAcceptedMessage, + unregister: UnregisterHandler +) { + const token = message.token; + + const answerDescription = new RTCSessionDescription(message); + transfer.pc.setRemoteDescription(answerDescription); + + const unregisterIce = onWithToken( + "ice_candidate", + token, + (message: RequestIceCandidateMessage) => + onIncomingIceCandidate(transfer, message) + ); + + unregisterIceOnComplete(transfer, unregisterIce); + + unregister(); +} + +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; }; 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, + 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([]); + + 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([]); - - return { - subscribe, - addRequest: request => update(state => [request, ...state]), - removeRequest: request => - update(state => state.filter(r => r !== request)), - }; -}; - -export default createRequestStore(); -- cgit 1.4.1