-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add bidirectional communication to exchange texts #108
Open
nidhal-labidi
wants to merge
1
commit into
main
Choose a base branch
from
feat/bidirectional-data-transfer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -16,6 +16,8 @@ type Listeners = { | |||||
'connecting-to-host': []; | ||||||
connected: []; | ||||||
'connection-impossible': []; | ||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
'receiving-data': [e: any]; | ||||||
done: []; | ||||||
disconnected: []; | ||||||
error: [e: string]; | ||||||
|
@@ -96,15 +98,8 @@ export class FlottformChannelClient extends EventEmitter<Listeners> { | |||||
this.logger.info(`ondatachannel: ${e.channel}`); | ||||||
this.changeState('connected'); | ||||||
this.dataChannel = e.channel; | ||||||
// Set the maximum amount of data waiting inside the datachannel's buffer | ||||||
this.dataChannel.bufferedAmountLowThreshold = this.BUFFER_THRESHOLD; | ||||||
// Set the listener to listen then emit an event when the buffer has more space available and can be used to send more data | ||||||
this.dataChannel.onbufferedamountlow = () => { | ||||||
this.emit('bufferedamountlow'); | ||||||
}; | ||||||
this.dataChannel.onopen = (e) => { | ||||||
this.logger.info(`ondatachannel - onopen: ${e.type}`); | ||||||
}; | ||||||
this.configureDataChannel(); | ||||||
this.setupDataChannelListener(); | ||||||
}; | ||||||
|
||||||
this.changeState('sending-client-info'); | ||||||
|
@@ -122,6 +117,37 @@ export class FlottformChannelClient extends EventEmitter<Listeners> { | |||||
this.changeState('disconnected'); | ||||||
}; | ||||||
|
||||||
private setupDataChannelListener = () => { | ||||||
if (this.dataChannel == null) { | ||||||
this.changeState( | ||||||
'error', | ||||||
'dataChannel is null. Unable to setup the listeners for the data channel' | ||||||
); | ||||||
return; | ||||||
} | ||||||
|
||||||
this.dataChannel.onmessage = (e) => { | ||||||
// Handling the incoming data from the Host depends on the use case. | ||||||
this.emit('receiving-data', e); | ||||||
}; | ||||||
}; | ||||||
|
||||||
private configureDataChannel = () => { | ||||||
if (this.dataChannel == null) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
this.changeState('error', 'dataChannel is null. Unable to setup the configure it!'); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return; | ||||||
} | ||||||
// Set the maximum amount of data waiting inside the datachannel's buffer | ||||||
this.dataChannel.bufferedAmountLowThreshold = this.BUFFER_THRESHOLD; | ||||||
// Set the listener to listen then emit an event when the buffer has more space available and can be used to send more data | ||||||
this.dataChannel.onbufferedamountlow = () => { | ||||||
this.emit('bufferedamountlow'); | ||||||
}; | ||||||
this.dataChannel.onopen = (e) => { | ||||||
this.logger.info(`ondatachannel - onopen: ${e.type}`); | ||||||
}; | ||||||
}; | ||||||
|
||||||
// sendData = (data: string | Blob | ArrayBuffer | ArrayBufferView) => { | ||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
sendData = (data: any) => { | ||||||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -21,6 +21,7 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> { | |||
private openPeerConnection: RTCPeerConnection | null = null; | ||||
private dataChannel: RTCDataChannel | null = null; | ||||
private pollForIceTimer: NodeJS.Timeout | number | null = null; | ||||
private BUFFER_THRESHOLD = 128 * 1024; // 128KB buffer threshold (maximum of 4 chunks in the buffer waiting to be sent over the network) | ||||
|
||||
constructor({ | ||||
flottformApi, | ||||
|
@@ -70,6 +71,11 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> { | |||
this.openPeerConnection = new RTCPeerConnection(this.rtcConfiguration); | ||||
|
||||
this.dataChannel = this.createDataChannel(); | ||||
if (this.dataChannel) { | ||||
//this.dataChannel.bufferedAmountLowThreshold = this.BUFFER_THRESHOLD; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dead code
Suggested change
|
||||
this.configureDataChannel(); | ||||
this.setupDataChannelListener(); | ||||
} | ||||
|
||||
const session = await this.openPeerConnection.createOffer(); | ||||
await this.openPeerConnection.setLocalDescription(session); | ||||
|
@@ -120,6 +126,40 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> { | |||
}; | ||||
}; | ||||
|
||||
private configureDataChannel = () => { | ||||
if (this.dataChannel == null) { | ||||
this.changeState('error', 'dataChannel is null. Unable to setup the configure it!'); | ||||
return; | ||||
} | ||||
// Set the maximum amount of data waiting inside the datachannel's buffer | ||||
this.dataChannel.bufferedAmountLowThreshold = this.BUFFER_THRESHOLD; | ||||
// Set the listener to listen then emit an event when the buffer has more space available and can be used to send more data | ||||
this.dataChannel.onbufferedamountlow = () => { | ||||
this.emit('bufferedamountlow'); | ||||
}; | ||||
this.dataChannel.onopen = (e) => { | ||||
this.logger.info(`ondatachannel - onopen: ${e.type}`); | ||||
}; | ||||
}; | ||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
sendData = (data: any) => { | ||||
if (this.dataChannel == null) { | ||||
this.changeState('error', 'dataChannel is null. Unable to send the data to the Client!'); | ||||
return; | ||||
} else if (!this.canSendMoreData()) { | ||||
this.logger.warn('Data channel is full! Cannot send data at the moment'); | ||||
return; | ||||
} | ||||
this.dataChannel.send(data); | ||||
}; | ||||
|
||||
canSendMoreData = () => { | ||||
return ( | ||||
this.dataChannel && | ||||
this.dataChannel.bufferedAmount < this.dataChannel.bufferedAmountLowThreshold | ||||
); | ||||
}; | ||||
|
||||
private setupHostIceGathering = ( | ||||
putHostInfoUrl: string, | ||||
hostKey: string, | ||||
|
@@ -279,7 +319,7 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> { | |||
|
||||
this.dataChannel.onerror = (e) => { | ||||
this.logger.log('channel.onerror', e); | ||||
this.changeState('error', { message: 'file-transfer' }); | ||||
this.changeState('error', { message: e.error.message }); | ||||
}; | ||||
}; | ||||
|
||||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
servers/demo/src/routes/flottform-messaging-client/[endpointId]/+page.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
<script lang="ts"> | ||
import { FlottformTextInputClient } from '@flottform/forms'; | ||
import { sdpExchangeServerBase } from '../../../api'; | ||
import { page } from '$app/stores'; | ||
import { onMount } from 'svelte'; | ||
|
||
let connectionStatus = $state<'init' | 'connected' | 'done' | 'disconnected' | 'error'>('init'); | ||
let error = $state<string>(''); | ||
let createWebRtcChannel: () => void; | ||
let sendMessage: (text: string) => void; | ||
let endConversation: () => void; | ||
|
||
let messageInput = $state<string>(''); | ||
let messages = $state<{ sender: string; text: string }[]>([]); | ||
|
||
function handleSend() { | ||
if (messageInput.trim()) { | ||
sendMessage(messageInput); | ||
messages = [...messages, { text: messageInput, sender: 'client' }]; | ||
messageInput = ''; | ||
} | ||
} | ||
onMount(async () => { | ||
const flottformTextInputClient = new FlottformTextInputClient({ | ||
endpointId: $page.params.endpointId, | ||
flottformApi: sdpExchangeServerBase | ||
}); | ||
|
||
flottformTextInputClient.start(); | ||
sendMessage = flottformTextInputClient.sendText; | ||
endConversation = flottformTextInputClient.close; | ||
|
||
flottformTextInputClient.on('connected', () => { | ||
connectionStatus = 'connected'; | ||
}); | ||
flottformTextInputClient.on('text-transfered', (textTransfered) => { | ||
// Nothing for now, just add the message to the list 'messages' which is done by the method 'handleSend' | ||
}); | ||
flottformTextInputClient.on('text-received', (textReceived) => { | ||
messages = [...messages, { text: textReceived, sender: 'host' }]; | ||
}); | ||
flottformTextInputClient.on('disconnected', () => { | ||
connectionStatus = 'disconnected'; | ||
}); | ||
flottformTextInputClient.on('error', (e) => { | ||
connectionStatus = 'error'; | ||
error = e; | ||
}); | ||
}); | ||
</script> | ||
|
||
<svelte:head> | ||
<title>Flottform DEMO</title> | ||
</svelte:head> | ||
|
||
<div class="min-h-screen flex items-center justify-center overflow-hidden bg-gray-50"> | ||
<div class="max-w-screen-xl w-full p-4 box-border flex flex-col items-center"> | ||
<h1 class="text-2xl font-bold text-gray-800 mb-8">Flottform Messaging - Client</h1> | ||
<div class="w-full max-w-md bg-white shadow-lg rounded-lg"> | ||
{#if connectionStatus === 'init'} | ||
<div class="flex flex-col items-center w-full p-8"> | ||
<p class="p-8 text-center">Trying to connect to the host...</p> | ||
</div> | ||
{:else if connectionStatus === 'connected'} | ||
<div class="flex flex-col rounded-lg border-[#ddd] h-[60vh]"> | ||
<div class="flex flex-col gap-3 flex-grow overflow-y-auto p-5"> | ||
{#if messages.length === 0} | ||
<p class="text-center italic"> | ||
You're connected to Host! You can start exchanging messages! | ||
</p> | ||
{/if} | ||
{#each messages as message} | ||
<div class="p-3 rounded-lg max-w-[70%] break-words {message.sender}"> | ||
<p>{message.text}</p> | ||
</div> | ||
{/each} | ||
</div> | ||
<div class="p-5 border-t border-[#ddd]"> | ||
<input | ||
type="text" | ||
class="border border-[#ddd] p-3 mb-3 w-full rounded" | ||
bind:value={messageInput} | ||
placeholder="Type your message..." | ||
onkeypress={(e) => e.key === 'Enter' && handleSend()} | ||
/> | ||
<div class="flex gap-3 flex-col sm:flex-row"> | ||
<button | ||
class="text-white px-5 py-2.5 rounded-md border-none cursor-pointer bg-[#007bff] hover:bg-[#0056b3]" | ||
onclick={handleSend}>Send</button | ||
> | ||
<button | ||
class="text-white px-5 py-2.5 rounded-md border-none cursor-pointer bg-[#dc3545] hover:bg-[#c82333]" | ||
onclick={endConversation} | ||
> | ||
End Conversation | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
{/if} | ||
{#if connectionStatus === 'disconnected'} | ||
<div class="flex flex-col items-center w-full gap-4 p-8"> | ||
<p class="text-center">Connection Channel Disconnected!</p> | ||
<p class="text-center"> | ||
Do want to connect one more time? Scan the QR code from the other peer or paste the link | ||
to the browser! | ||
</p> | ||
</div> | ||
{/if} | ||
{#if connectionStatus === 'error'} | ||
<div class="flex flex-col items-center w-full gap-4 p-8"> | ||
<p class="text-center text-red-500"> | ||
Connection Failed with the following error: {error} | ||
</p> | ||
<button | ||
onclick={createWebRtcChannel} | ||
class="bg-[#0079b2] hover:bg-[#007bff] text-white px-4 py-2 rounded-lg text-base" | ||
>Try to Connect Again</button | ||
> | ||
</div> | ||
{/if} | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<style> | ||
.client { | ||
align-self: flex-end; | ||
background-color: #007bff; | ||
color: white; | ||
} | ||
|
||
.host { | ||
align-self: flex-start; | ||
background-color: #e9ecef; | ||
color: black; | ||
} | ||
</style> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either
=== null
or something like this: