Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions flottform/forms/src/flottform-channel-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {

export class FlottformChannelHost extends EventEmitter<FlottformEventMap> {
private flottformApi: string | URL;
private baseApi: string;
private endpointId: string = '';
private hostKey: string = '';
private createClientUrl: (params: { endpointId: string }) => Promise<string>;
private rtcConfiguration: RTCConfiguration;
private pollTimeForIceInMs: number;
Expand All @@ -35,6 +38,11 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> {
}) {
super();
this.flottformApi = flottformApi;
this.baseApi = (
this.flottformApi instanceof URL ? this.flottformApi : new URL(this.flottformApi)
)
.toString()
.replace(/\/$/, '');
this.createClientUrl = createClientUrl;
this.rtcConfiguration = DEFAULT_WEBRTC_CONFIG;
this.pollTimeForIceInMs = pollTimeForIceInMs;
Expand All @@ -55,14 +63,9 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> {
if (this.openPeerConnection) {
this.close();
}
const baseApi = (
this.flottformApi instanceof URL ? this.flottformApi : new URL(this.flottformApi)
)
.toString()
.replace(/\/$/, '');

try {
this.rtcConfiguration.iceServers = await this.fetchIceServers(baseApi);
this.rtcConfiguration.iceServers = await this.fetchIceServers(this.baseApi);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing this.baseApi shouldn't be necessary if we use this.baseApi in the function instead of a parameter

} catch (error) {
// Use the default configuration as a fallback
this.logger.error(error);
Expand All @@ -74,11 +77,13 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> {
const session = await this.openPeerConnection.createOffer();
await this.openPeerConnection.setLocalDescription(session);

const { endpointId, hostKey } = await this.createEndpoint(baseApi, session);
const { endpointId, hostKey } = await this.createEndpoint(this.baseApi, session);
this.hostKey = hostKey;
this.endpointId = endpointId;
this.logger.log('Created endpoint', { endpointId, hostKey });

const getEndpointInfoUrl = `${baseApi}/${endpointId}`;
const putHostInfoUrl = `${baseApi}/${endpointId}/host`;
const getEndpointInfoUrl = `${this.baseApi}/${endpointId}`;
const putHostInfoUrl = `${this.baseApi}/${endpointId}/host`;

const hostIceCandidates = new Set<RTCIceCandidateInit>();
await this.putHostInfo(putHostInfoUrl, hostKey, hostIceCandidates, session);
Expand All @@ -103,6 +108,8 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> {
this.openPeerConnection = null;
}
this.changeState('disconnected');
// Cleanup old entries.
this.deleteEndpoint(this.baseApi, this.endpointId, this.hostKey);
};

private setupDataChannelListener = () => {
Expand Down Expand Up @@ -223,6 +230,19 @@ export class FlottformChannelHost extends EventEmitter<FlottformEventMap> {
return response.json();
};

private deleteEndpoint = async (baseApi: string, endpointId: string, hostKey: string) => {
const response = await fetch(`${baseApi}/${endpointId}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ hostKey })
});

return response.json();
};

private fetchIceServers = async (baseApi: string) => {
const response = await fetch(`${baseApi}/ice-server-credentials`, {
method: 'GET',
Expand Down
1 change: 1 addition & 0 deletions flottform/forms/src/flottform-text-input-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class FlottformTextInputHost extends BaseInputHost<Listeners> {
this.emit('receive');
// We suppose that the data received is small enough to be all included in 1 message
this.emit('done', e.data);
this.close();
};

private registerListeners = () => {
Expand Down
45 changes: 41 additions & 4 deletions flottform/server/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type EndpointInfo = {
session: RTCSessionDescriptionInit;
iceCandidates: RTCIceCandidateInit[];
};
lastUpdate: number;
};
type SafeEndpointInfo = Omit<EndpointInfo, 'hostKey' | 'clientKey'>;

Expand All @@ -25,8 +26,40 @@ function createRandomEndpointId(): string {

class FlottformDatabase {
private map = new Map<EndpointId, EndpointInfo>();
private cleanupIntervalId: NodeJS.Timeout | null = null;
private cleanupPeriod = 30 * 60 * 1000; // (30 minutes)
private entryTTL = 25 * 60 * 1000; // Time-to-Live for each entry (25 minutes)

constructor() {}
constructor() {
this.startCleanup();
}

private startCleanup() {
this.cleanupIntervalId = setInterval(() => {
// Loop over all entries and delete the stale ones.
const now = Date.now();
for (const [endpointId, endpointInfo] of this.map) {
const lastUpdated = endpointInfo.lastUpdate;
if (now - lastUpdated > this.entryTTL) {
this.map.delete(endpointId);
console.log(`Cleaned up stale entry: ${endpointId}`);
}
}
}, this.cleanupPeriod);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.cleanupIntervalId = setInterval(() => {
// Loop over all entries and delete the stale ones.
const now = Date.now();
for (const [endpointId, endpointInfo] of this.map) {
const lastUpdated = endpointInfo.lastUpdate;
if (now - lastUpdated > this.entryTTL) {
this.map.delete(endpointId);
console.log(`Cleaned up stale entry: ${endpointId}`);
}
}
}, this.cleanupPeriod);
const cleanupFn = () => {
const nextStartTime = Date.now() + this.cleanupPeriod;
// Loop over all entries and delete the stale ones.
const now = Date.now();
for (const [endpointId, endpointInfo] of this.map) {
const lastUpdated = endpointInfo.lastUpdate;
if (now - lastUpdated > this.entryTTL) {
this.map.delete(endpointId);
console.log(`Cleaned up stale entry: ${endpointId}`);
}
}
this.cleanupIntervalId = setTimeout(cleanupFn, Math.max(0, Date.now - nextStartTime));
};
this.cleanupIntervalId = setTimeout(cleanupFn, this.cleanupPeriod);

}

private stopCleanup() {
// Clear the interval to stop cleanup
if (this.cleanupIntervalId) {
clearInterval(this.cleanupIntervalId);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
clearInterval(this.cleanupIntervalId);
clearTimeout(this.cleanupIntervalId);

this.cleanupIntervalId = null;
}
}

// Stop the cleanup when the database is no longer needed
destroy() {
this.stopCleanup();
}

async createEndpoint({ session }: { session: RTCSessionDescriptionInit }): Promise<EndpointInfo> {
const entry = {
Expand All @@ -35,7 +68,8 @@ class FlottformDatabase {
hostInfo: {
session,
iceCandidates: []
}
},
lastUpdate: Date.now()
};
this.map.set(entry.endpointId, entry);
return entry;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exposing lastUpdate, see comment below

Expand All @@ -46,6 +80,7 @@ class FlottformDatabase {
if (!entry) {
throw Error('Endpoint not found');
}
entry.lastUpdate = Date.now();
const { hostKey: _ignore1, clientKey: _ignore2, ...endpoint } = entry;

return endpoint;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lastUpdate should not be exposed to the client as it should only be used internally for cleanup

Expand All @@ -72,7 +107,8 @@ class FlottformDatabase {

const newInfo = {
...existingSession,
hostInfo: { ...existingSession.hostInfo, session, iceCandidates }
hostInfo: { ...existingSession.hostInfo, session, iceCandidates },
lastUpdate: Date.now()
};
this.map.set(endpointId, newInfo);

Expand Down Expand Up @@ -105,7 +141,8 @@ class FlottformDatabase {
const newInfo = {
...existingSession,
clientKey,
clientInfo: { session, iceCandidates }
clientInfo: { session, iceCandidates },
lastUpdate: Date.now()
};
this.map.set(endpointId, newInfo);

Expand Down