more progress

pull/134/head
Alex Kern 12 months ago
parent bb151ad41a
commit 248df834c1
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

@ -1,5 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
// Disable strict mode to avoid calling useEffect twice in development.
// The uploader and downloader are both using useEffect to listen for peerjs events
// which causes the connection to be created twice.
reactStrictMode: false,
}
module.exports = nextConfig

@ -17,6 +17,8 @@ export function ConnectionListItem({
return 'bg-green-500 dark:bg-green-600'
case UploaderConnectionStatus.Closed:
return 'bg-red-500 dark:bg-red-600'
case UploaderConnectionStatus.InvalidPassword:
return 'bg-red-500 dark:bg-red-600'
default:
return 'bg-stone-500 dark:bg-stone-600'
}
@ -24,7 +26,8 @@ export function ConnectionListItem({
return (
<div className="w-full mt-4">
<div className="flex items-center space-x-2 mb-2">
<div className="flex justify-between items-center mb-2">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium">
{conn.browserName && conn.browserVersion ? (
<>
@ -40,9 +43,21 @@ export function ConnectionListItem({
conn.status,
)}`}
>
{conn.status}
{conn.status.replace(/_/g, ' ')}
</span>
</div>
<div className="text-sm text-stone-500 dark:text-stone-400">
<div>
Completed: {conn.completedFiles} / {conn.totalFiles} files
</div>
{conn.uploadingFileName && conn.status === UploaderConnectionStatus.Uploading && (
<div>
Current file: {Math.round(conn.currentFileProgress * 100)}%
</div>
)}
</div>
</div>
<ProgressBar
value={
(conn.completedFiles + conn.currentFileProgress) / conn.totalFiles

@ -1,7 +1,7 @@
export function ErrorMessage({ message }: { message: string }): JSX.Element {
return (
<div
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
className="bg-red-100 dark:bg-red-900/30 border border-red-400 dark:border-red-700 text-red-700 dark:text-red-300 px-4 py-3 rounded relative"
role="alert"
>
<span className="block sm:inline">{message}</span>

@ -122,7 +122,14 @@ export function useDownloader(uploaderPeerID: string): {
peer.on('error', handleError)
return () => {
if (conn.open) conn.close()
if (conn.open) {
conn.close()
} else {
conn.once('open', () => {
conn.close()
})
}
conn.off('open', handleOpen)
conn.off('data', handleData)
conn.off('error', handleError)

@ -81,6 +81,26 @@ export function useUploaderChannel(
}
}, [secret, shortSlug, renewMutation, renewInterval])
useEffect(() => {
if (!shortSlug || !secret) return
const handleUnload = (): void => {
// Using sendBeacon for best-effort delivery during page unload
navigator.sendBeacon(
'/api/destroy',
JSON.stringify({ slug: shortSlug })
)
}
window.addEventListener('beforeunload', handleUnload)
window.addEventListener('unload', handleUnload)
return () => {
window.removeEventListener('beforeunload', handleUnload)
window.removeEventListener('unload', handleUnload)
}
}, [shortSlug, secret])
return {
isLoading,
error,

@ -32,9 +32,9 @@ export function useUploaderConnections(
): Array<UploaderConnection> {
const [connections, setConnections] = useState<Array<UploaderConnection>>([])
console.log('connections', connections)
useEffect(() => {
const cleanupHandlers: Array<() => void> = []
const listener = (conn: DataConnection) => {
// If the connection is a report, we need to hard-redirect the uploader to the reported page to prevent them from uploading more files.
if (conn.metadata?.type === 'report') {
@ -61,14 +61,9 @@ export function useUploaderConnections(
}
setConnections((conns) => {
// Check if connection already exists
const exists = conns.some(conn => conn.dataConnection === newConn.dataConnection)
if (exists) {
console.log('connection already exists!', newConn.dataConnection)
return conns
}
return [newConn, ...conns]
})
const updateConnection = (
fn: (c: UploaderConnection) => UploaderConnection,
) => {
@ -77,7 +72,7 @@ export function useUploaderConnections(
)
}
conn.on('data', (data): void => {
const onData = (data: any): void => {
try {
const message = decodeMessage(data)
switch (message.type) {
@ -120,7 +115,7 @@ export function useUploaderConnections(
return {
...draft,
...newConnectionState,
status: UploaderConnectionStatus.Paused,
status: UploaderConnectionStatus.Ready,
}
})
@ -146,14 +141,15 @@ export function useUploaderConnections(
if (submittedPassword === password) {
updateConnection((draft) => {
if (
draft.status !== UploaderConnectionStatus.Authenticating
draft.status !== UploaderConnectionStatus.Authenticating &&
draft.status !== UploaderConnectionStatus.InvalidPassword
) {
return draft
}
return {
...draft,
status: UploaderConnectionStatus.Paused,
status: UploaderConnectionStatus.Ready,
}
})
@ -196,21 +192,9 @@ export function useUploaderConnections(
const fileName = message.fileName
let offset = message.offset
const file = validateOffset(files, fileName, offset)
updateConnection((draft) => {
if (draft.status !== UploaderConnectionStatus.Paused) {
return draft
}
return {
...draft,
status: UploaderConnectionStatus.Uploading,
uploadingFileName: fileName,
uploadingOffset: offset,
currentFileProgress: offset / file.size,
}
})
const sendNextChunk = () => {
const sendNextChunkAsync = () => {
sendChunkTimeout = setTimeout(() => {
const end = Math.min(file.size, offset + MAX_CHUNK_SIZE)
const chunkSize = end - offset
const final = chunkSize < MAX_CHUNK_SIZE
@ -226,16 +210,15 @@ export function useUploaderConnections(
updateConnection((draft) => {
offset = end
if (final) {
console.log('final chunk', draft.completedFiles + 1)
return {
...draft,
status: UploaderConnectionStatus.Paused,
status: UploaderConnectionStatus.Ready,
completedFiles: draft.completedFiles + 1,
currentFileProgress: 0,
}
} else {
sendChunkTimeout = setTimeout(() => {
sendNextChunk()
}, 0)
sendNextChunkAsync()
return {
...draft,
uploadingOffset: end,
@ -243,8 +226,24 @@ export function useUploaderConnections(
}
}
})
}, 0)
}
updateConnection((draft) => {
if (draft.status !== UploaderConnectionStatus.Ready && draft.status !== UploaderConnectionStatus.Paused) {
return draft
}
sendNextChunkAsync()
return {
...draft,
status: UploaderConnectionStatus.Uploading,
uploadingFileName: fileName,
uploadingOffset: offset,
currentFileProgress: offset / file.size,
}
sendNextChunk()
})
break
}
@ -270,7 +269,7 @@ export function useUploaderConnections(
case MessageType.Done: {
updateConnection((draft) => {
if (draft.status !== UploaderConnectionStatus.Paused) {
if (draft.status !== UploaderConnectionStatus.Ready) {
return draft
}
@ -286,9 +285,9 @@ export function useUploaderConnections(
} catch (err) {
console.error(err)
}
})
}
conn.on('close', (): void => {
const onClose = (): void => {
if (sendChunkTimeout) {
clearTimeout(sendChunkTimeout)
}
@ -308,6 +307,15 @@ export function useUploaderConnections(
status: UploaderConnectionStatus.Closed,
}
})
}
conn.on('data', onData)
conn.on('close', onClose)
cleanupHandlers.push(() => {
conn.off('data', onData)
conn.off('close', onClose)
conn.close()
})
}
@ -315,6 +323,7 @@ export function useUploaderConnections(
return () => {
peer.off('connection', listener)
cleanupHandlers.forEach((fn) => fn())
}
}, [peer, files, password])

@ -4,6 +4,7 @@ export type UploadedFile = File & { entryFullPath?: string }
export enum UploaderConnectionStatus {
Pending = 'PENDING',
Ready = 'READY',
Paused = 'PAUSED',
Uploading = 'UPLOADING',
Done = 'DONE',

Loading…
Cancel
Save