edge cases

This commit is contained in:
Klemek
2025-03-19 16:28:13 +01:00
parent 93f2287c13
commit 3b81699ff4
3 changed files with 182 additions and 20 deletions
+1 -1
View File
@@ -15,6 +15,6 @@
* [x] QRCode * [x] QRCode
* [x] Fix turn server settings to work on all servers * [x] Fix turn server settings to work on all servers
* [x] More cleaning * [x] More cleaning
* [ ] Fix edge cases * [x] Fix edge cases
* [ ] Cancel download * [ ] Cancel download
+9 -4
View File
@@ -48,7 +48,8 @@
</div> </div>
<div class="mt-2 w-full"> <div class="mt-2 w-full">
<div class="inline-grid *:[grid-area:1/1] align-middle mr-1"> <div class="inline-grid *:[grid-area:1/1] align-middle mr-1">
<div :class="`status status-${statusColor(status)} status-lg animate-ping`"></div> <div v-if="statusBlinking(status)" :class="`status status-${statusColor(status)} status-lg animate-ping`">
</div>
<div :class="`status status-${statusColor(status)} status-lg`"></div> <div :class="`status status-${statusColor(status)} status-lg`"></div>
</div> {{ status }} </div> {{ status }}
</div> </div>
@@ -67,14 +68,18 @@
<div v-if="serverIsReady" @click="onShare" class="mt-2 w-full btn btn-primary"> <div v-if="serverIsReady" @click="onShare" class="mt-2 w-full btn btn-primary">
<i icon="share-2"></i> {{ serverShareText }} <i icon="share-2"></i> {{ serverShareText }}
</div> </div>
<ul v-if="serverIsReady && server.clients.length" <ul v-if="serverIsReady && server.clients.length" class="mt-2 -mx-10 -mb-10 list rounded-box text-left">
class="mt-2 list bg-base-100 rounded-box shadow-md text-left">
<li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Connected peers</li> <li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Connected peers</li>
<li class="list-row" v-for="client in server.clients" v-bind:key="client.id"> <li class="list-row" v-for="client in server.clients" v-bind:key="client.id">
<div class="list-col-grow"> <div class="list-col-grow">
<div>{{ client.userAgent ?? client.id }}</div> <div>{{ client.userAgent ?? client.id }}</div>
<div> <div>
<div :class="`status status-${statusColor(client.status)}`"></div> {{ client.status }} <div class="inline-grid *:[grid-area:1/1] align-middle mr-1">
<div v-if="statusBlinking(client.status)"
:class="`status status-${statusColor(client.status)} animate-ping`">
</div>
<div :class="`status status-${statusColor(client.status)}`"></div>
</div> {{ client.status }}
</div> </div>
<progress :value="client.sent" :max="fileSize" aria-busy="!client.connected" class="progress" <progress :value="client.sent" :max="fileSize" aria-busy="!client.connected" class="progress"
:class="client.sent >= fileSize ? 'progress-success' : 'progress-primary'"></progress> :class="client.sent >= fileSize ? 'progress-success' : 'progress-primary'"></progress>
+172 -15
View File
@@ -85,6 +85,7 @@ const MESSAGE_TYPE = {
ClientInfo: "client-info", ClientInfo: "client-info",
ClientSeek: "client-seek", ClientSeek: "client-seek",
ClientDone: "client-done", ClientDone: "client-done",
Ping: "ping",
}; };
const STATUS = { const STATUS = {
@@ -108,12 +109,79 @@ const STATUS_COLOR = {
[STATUS.ClientConnecting]: "info", [STATUS.ClientConnecting]: "info",
[STATUS.ClientWaiting]: "info", [STATUS.ClientWaiting]: "info",
[STATUS.ClientReady]: "success", [STATUS.ClientReady]: "success",
[STATUS.ClientDownloading]: "info", [STATUS.ClientDownloading]: "primary",
[STATUS.ClientDownloaded]: "success", [STATUS.ClientDownloaded]: "success",
[STATUS.ClientDisconnected]: "neutral", [STATUS.ClientDisconnected]: "neutral",
}; };
const CONNECTED_STATUSES = [
STATUS.Connecting,
STATUS.ServerNoFile,
STATUS.ServerReady,
STATUS.ClientConnecting,
STATUS.ClientWaiting,
STATUS.ClientReady,
];
const PEER_ERROR = {
/**
* The client's browser does not support some or all WebRTC features that you are trying to use.
*/
BrowserIncompatible: "browser-incompatible",
/**
* You've already disconnected this peer from the server and can no longer make any new connections on it.
*/
Disconnected: "disconnected",
/**
* The ID passed into the Peer constructor contains illegal characters.
*/
InvalidID: "invalid-id",
/**
* The API key passed into the Peer constructor contains illegal characters or is not in the system (cloud server only).
*/
InvalidKey: "invalid-key",
/**
* Lost or cannot establish a connection to the signalling server.
*/
Network: "network",
/**
* The peer you're trying to connect to does not exist.
*/
PeerUnavailable: "peer-unavailable",
/**
* PeerJS is being used securely, but the cloud server does not support SSL. Use a custom PeerServer.
*/
SslUnavailable: "ssl-unavailable",
/**
* Unable to reach the server.
*/
ServerError: "server-error",
/**
* An error from the underlying socket.
*/
SocketError: "socket-error",
/**
* The underlying socket closed unexpectedly.
*/
SocketClosed: "socket-closed",
/**
* The ID passed into the Peer constructor is already taken.
*
* :::caution
* This error is not fatal if your peer has open peer-to-peer connections.
* This can happen if you attempt to {@apilink Peer.reconnect} a peer that has been disconnected from the server,
* but its old ID has now been taken.
* :::
*/
UnavailableID: "unavailable-id",
/**
* Native WebRTC errors.
*/
WebRTC: "webrtc",
};
const MAX_CHUNK_SIZE = 12 * 1024; const MAX_CHUNK_SIZE = 12 * 1024;
const MAX_DELAY_PING = 5000;
const app = createApp({ const app = createApp({
data() { data() {
@@ -139,6 +207,7 @@ const app = createApp({
downloadEnd: null, downloadEnd: null,
received: [], received: [],
buffer: null, buffer: null,
lastMessage: null,
}, },
}; };
}, },
@@ -148,7 +217,7 @@ const app = createApp({
}, },
serverIsReady() { serverIsReady() {
return ( return (
this.error !== null && this.canConnect && this.server.data !== null this.error === null && this.canConnect && this.server.data !== null
); );
}, },
serverShareText() { serverShareText() {
@@ -162,7 +231,7 @@ const app = createApp({
}, },
clientIsReady() { clientIsReady() {
return ( return (
this.error !== null && this.error === null &&
this.canConnect && this.canConnect &&
this.client.connection !== null && this.client.connection !== null &&
this.client.buffer !== null && this.client.buffer !== null &&
@@ -216,6 +285,9 @@ const app = createApp({
const time = const time =
(this.client.downloadEnd ?? new Date()) - this.client.downloadStart; (this.client.downloadEnd ?? new Date()) - this.client.downloadStart;
const speed = this.clientDownloadProgress / time; const speed = this.clientDownloadProgress / time;
if (speed <= 0) {
return "Unknown";
}
const remainingBytes = this.fileSize - this.clientDownloadProgress; const remainingBytes = this.fileSize - this.clientDownloadProgress;
const remainingTime = remainingBytes / speed; const remainingTime = remainingBytes / speed;
return `${utils.prettyTime(remainingTime)}`; return `${utils.prettyTime(remainingTime)}`;
@@ -247,6 +319,9 @@ const app = createApp({
if (uuidRegex.test(remoteId)) { if (uuidRegex.test(remoteId)) {
this.isServer = false; this.isServer = false;
this.client.remoteId = remoteId; this.client.remoteId = remoteId;
} else if (remoteId !== "") {
this.isServer = false;
this.error = "Invalid link";
} }
}, },
initPeer() { initPeer() {
@@ -280,6 +355,14 @@ const app = createApp({
this.peer.on("error", this.onPeerError); this.peer.on("error", this.onPeerError);
}, },
initServerConnection(conn) { initServerConnection(conn) {
const index = this.initServerClient(conn);
conn.on("open", () => this.onServerConnectionOpen(index));
conn.on("close", () => this.onServerConnectionClose(index));
conn.on("data", (data) => this.onServerConnectionData(index, data));
conn.on("error", (err) => this.onServerConnectionError(index, err));
this.initServerWatch(index);
},
initServerClient(conn) {
let index = this.server.clients.findIndex( let index = this.server.clients.findIndex(
(client) => client.id === conn.peer (client) => client.id === conn.peer
); );
@@ -291,6 +374,7 @@ const app = createApp({
connected: false, connected: false,
status: STATUS.ClientConnecting, status: STATUS.ClientConnecting,
userAgent: null, userAgent: null,
lastMessage: new Date(),
}; };
if (index === -1) { if (index === -1) {
index = this.server.clients.length; index = this.server.clients.length;
@@ -298,10 +382,20 @@ const app = createApp({
} else { } else {
this.server.clients[index] = clientData; this.server.clients[index] = clientData;
} }
conn.on("open", () => this.onServerConnectionOpen(index)); return index;
conn.on("close", () => this.onServerConnectionClose(index)); },
conn.on("data", (data) => this.onServerConnectionData(index, data)); initServerWatch(index) {
conn.on("error", (err) => this.onServerConnectionError(index, err)); setInterval(() => {
if (!this.error && this.server.clients[index].connected) {
this.sendServerPing(index);
if (
new Date() - this.server.clients[index].lastMessage >
MAX_DELAY_PING
) {
this.onServerConnectionClose(index);
}
}
}, 1000);
}, },
initClientConnection(conn) { initClientConnection(conn) {
this.client.connection = conn; this.client.connection = conn;
@@ -309,6 +403,17 @@ const app = createApp({
conn.on("close", this.onClientConnectionClose); conn.on("close", this.onClientConnectionClose);
conn.on("data", this.onClientConnectionData); conn.on("data", this.onClientConnectionData);
conn.on("error", this.onClientConnectionError); conn.on("error", this.onClientConnectionError);
this.initClientWatch();
},
initClientWatch() {
setInterval(() => {
if (!this.error && this.client.connected) {
this.sendClientPing();
if (new Date() - this.client.lastMessage > MAX_DELAY_PING) {
this.onClientConnectionClose();
}
}
}, 1000);
}, },
clientCreateStream() { clientCreateStream() {
try { try {
@@ -334,14 +439,16 @@ const app = createApp({
statusColor(status) { statusColor(status) {
return STATUS_COLOR[status]; return STATUS_COLOR[status];
}, },
statusBlinking(status) {
return CONNECTED_STATUSES.includes(status);
},
// PEER EVENTS // PEER EVENTS
onPeerOpen(id) { onPeerOpen(id) {
this.localId = id; this.localId = id;
this.error = null;
if (this.isServer) { if (this.isServer) {
this.server.url = `${window.location.href}?s=${id}`; this.server.url = `${window.location.href}?s=${id}`;
utils.makeQrCode(this.$refs.qrcode, this.server.url); utils.makeQrCode(this.$refs.qrcode, this.server.url);
} else { } else if (this.error === null) {
this.clientOpenConnection(); this.clientOpenConnection();
} }
}, },
@@ -351,15 +458,34 @@ const app = createApp({
} }
}, },
onPeerClose() { onPeerClose() {
// Window shutting down
this.peer = null; this.peer = null;
}, },
onPeerDisconnected() { onPeerDisconnected() {
this.peer.reconnect(); if (this.peer) {
this.error = `Disconnected.<br>Reconnecting...`;
this.peer.reconnect();
}
}, },
onPeerError(err) { onPeerError(err) {
this.error = `${err.type}.<br>Reconnecting...`; switch (err.type) {
this.peer = null; case PEER_ERROR.PeerUnavailable:
setTimeout(this.initPeer, 1000); if (!this.isServer) {
this.error = `This link is no longer available`;
}
break;
case PEER_ERROR.SocketClosed:
if (!this.isServer) {
this.error = `The remote peer closed the page`;
}
break;
default:
if (this.peer) {
this.error = `${err.type}.<br>Reconnecting...`;
this.peer.reconnect();
}
break;
}
}, },
// SERVER CONNECTION EVENTS // SERVER CONNECTION EVENTS
onServerConnectionOpen(index) { onServerConnectionOpen(index) {
@@ -368,7 +494,10 @@ const app = createApp({
this.sendServerInfo(index); this.sendServerInfo(index);
}, },
onServerConnectionData(index, data) { onServerConnectionData(index, data) {
this.server.clients[index].lastMessage = new Date();
switch (data.type) { switch (data.type) {
case MESSAGE_TYPE.Ping:
break;
case MESSAGE_TYPE.ClientInfo: case MESSAGE_TYPE.ClientInfo:
this.handleClientInfo(index, data); this.handleClientInfo(index, data);
break; break;
@@ -385,9 +514,11 @@ const app = createApp({
}, },
onServerConnectionClose(index) { onServerConnectionClose(index) {
this.server.clients[index].status = STATUS.ClientDisconnected; this.server.clients[index].status = STATUS.ClientDisconnected;
this.server.clients[index].connected = false;
}, },
onServerConnectionError(index) { onServerConnectionError(index) {
this.server.clients[index].status = STATUS.Error; this.server.clients[index].status = STATUS.Error;
this.server.clients[index].connected = false;
}, },
// CLIENT CONNECTION EVENTS // CLIENT CONNECTION EVENTS
onClientConnectionOpen() { onClientConnectionOpen() {
@@ -395,7 +526,10 @@ const app = createApp({
this.sendClientInfo(); this.sendClientInfo();
}, },
onClientConnectionData(data) { onClientConnectionData(data) {
this.client.lastMessage = new Date();
switch (data.type) { switch (data.type) {
case MESSAGE_TYPE.Ping:
break;
case MESSAGE_TYPE.ServerInfo: case MESSAGE_TYPE.ServerInfo:
this.handleServerInfo(data); this.handleServerInfo(data);
break; break;
@@ -412,12 +546,30 @@ const app = createApp({
}, },
onClientConnectionClose() { onClientConnectionClose() {
this.client.connection = null; this.client.connection = null;
if (!this.clientFinished) {
this.error = `The remote peer closed the page`;
}
}, },
onClientConnectionError(err) { onClientConnectionError(err) {
this.error = `${err.type}.<br>Reconnecting...`; switch (err.type) {
setTimeout(this.clientOpenConnection, 1000); case PEER_ERROR.PeerUnavailable:
this.error = `This link is no longer available`;
break;
case PEER_ERROR.SocketClosed:
this.error = `The remote peer closed the page`;
break;
default:
this.error = `${err.type}.<br>Reconnecting...`;
setTimeout(this.clientOpenConnection);
break;
}
}, },
// EXCHANGES // EXCHANGES
sendServerPing(index) {
this.server.clients[index].connection.send({
type: MESSAGE_TYPE.Ping,
});
},
sendServerInfo(index) { sendServerInfo(index) {
this.server.clients[index].connection.send({ this.server.clients[index].connection.send({
type: MESSAGE_TYPE.ServerInfo, type: MESSAGE_TYPE.ServerInfo,
@@ -465,6 +617,11 @@ const app = createApp({
this.clientDownloadFile(); this.clientDownloadFile();
} }
}, },
sendClientPing() {
this.client.connection.send({
type: MESSAGE_TYPE.Ping,
});
},
sendClientInfo() { sendClientInfo() {
this.client.connection.send({ this.client.connection.send({
type: MESSAGE_TYPE.ClientInfo, type: MESSAGE_TYPE.ClientInfo,