pull/268/merge
Fares Abawi 3 months ago committed by GitHub
commit 6f1b042c65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,7 +2,7 @@ import { NextResponse } from 'next/server'
import { getOrCreateChannelRepo } from '../../../channel' import { getOrCreateChannelRepo } from '../../../channel'
export async function POST(request: Request): Promise<NextResponse> { export async function POST(request: Request): Promise<NextResponse> {
const { uploaderPeerID } = await request.json() const { uploaderPeerID, sharedSlug } = await request.json()
if (!uploaderPeerID) { if (!uploaderPeerID) {
return NextResponse.json( return NextResponse.json(
@ -11,6 +11,6 @@ export async function POST(request: Request): Promise<NextResponse> {
) )
} }
const channel = await getOrCreateChannelRepo().createChannel(uploaderPeerID) const channel = await getOrCreateChannelRepo().createChannel(uploaderPeerID, undefined, sharedSlug)
return NextResponse.json(channel) return NextResponse.json(channel)
} }

@ -4,6 +4,7 @@ import { getOrCreateChannelRepo } from '../../../channel'
import Spinner from '../../../components/Spinner' import Spinner from '../../../components/Spinner'
import Wordmark from '../../../components/Wordmark' import Wordmark from '../../../components/Wordmark'
import Downloader from '../../../components/Downloader' import Downloader from '../../../components/Downloader'
import MultiDownloader from '../../../components/MultiDownloader'
import WebRTCPeerProvider from '../../../components/WebRTCProvider' import WebRTCPeerProvider from '../../../components/WebRTCProvider'
import ReportTermsViolationButton from '../../../components/ReportTermsViolationButton' import ReportTermsViolationButton from '../../../components/ReportTermsViolationButton'
@ -33,7 +34,14 @@ export default async function DownloadPage({
<Spinner direction="down" /> <Spinner direction="down" />
<Wordmark /> <Wordmark />
<WebRTCPeerProvider> <WebRTCPeerProvider>
<Downloader uploaderPeerID={channel.uploaderPeerID} /> {channel.additionalUploaders && channel.additionalUploaders.length > 0 ? (
<MultiDownloader
primaryUploaderID={channel.uploaderPeerID}
additionalUploaders={channel.additionalUploaders}
/>
) : (
<Downloader uploaderPeerID={channel.uploaderPeerID} />
)}
<ReportTermsViolationButton <ReportTermsViolationButton
uploaderPeerID={channel.uploaderPeerID} uploaderPeerID={channel.uploaderPeerID}
slug={slug} slug={slug}
@ -41,4 +49,4 @@ export default async function DownloadPage({
</WebRTCPeerProvider> </WebRTCPeerProvider>
</div> </div>
) )
} }

@ -1,18 +1,18 @@
'use client' 'use client'
import React, { JSX, useCallback, useState } from 'react' import React, { JSX, useCallback, useState, useMemo } from 'react'
import { getFileName } from '../fs'
import { UploadedFile } from '../types'
import WebRTCPeerProvider from '../components/WebRTCProvider' import WebRTCPeerProvider from '../components/WebRTCProvider'
import DropZone from '../components/DropZone' import DropZone from '../components/DropZone'
import UploadFileList from '../components/UploadFileList' import UploadFileList from '../components/UploadFileList'
import Uploader from '../components/Uploader' import Uploader from '../components/Uploader'
import PasswordField from '../components/PasswordField' import PasswordField from '../components/PasswordField'
import SharedLinkField from '../components/SharedLinkField'
import StartButton from '../components/StartButton' import StartButton from '../components/StartButton'
import { UploadedFile } from '../types'
import Spinner from '../components/Spinner' import Spinner from '../components/Spinner'
import Wordmark from '../components/Wordmark' import Wordmark from '../components/Wordmark'
import CancelButton from '../components/CancelButton' import CancelButton from '../components/CancelButton'
import { useMemo } from 'react'
import { getFileName } from '../fs'
import TitleText from '../components/TitleText' import TitleText from '../components/TitleText'
import SubtitleText from '../components/SubtitleText' import SubtitleText from '../components/SubtitleText'
import { pluralize } from '../utils/pluralize' import { pluralize } from '../utils/pluralize'
@ -54,10 +54,24 @@ function useUploaderFileListData(uploadedFiles: UploadedFile[]) {
}, [uploadedFiles]) }, [uploadedFiles])
} }
function extractSlugFromLink(link: string): string | undefined {
if (!link) return undefined
try {
const url = new URL(link)
const pathParts = url.pathname.split('/')
return pathParts[pathParts.length - 1]
} catch {
return link.trim() ? link.trim() : undefined
}
}
function ConfirmUploadState({ function ConfirmUploadState({
uploadedFiles, uploadedFiles,
password, password,
sharedLink,
onChangePassword, onChangePassword,
onChangeSharedLink,
onCancel, onCancel,
onStart, onStart,
onRemoveFile, onRemoveFile,
@ -65,7 +79,9 @@ function ConfirmUploadState({
}: { }: {
uploadedFiles: UploadedFile[] uploadedFiles: UploadedFile[]
password: string password: string
sharedLink: string
onChangePassword: (pw: string) => void onChangePassword: (pw: string) => void
onChangeSharedLink: (link: string) => void
onCancel: () => void onCancel: () => void
onStart: () => void onStart: () => void
onRemoveFile: (index: number) => void onRemoveFile: (index: number) => void
@ -81,6 +97,7 @@ function ConfirmUploadState({
</TitleText> </TitleText>
<UploadFileList files={fileListData} onRemove={onRemoveFile} /> <UploadFileList files={fileListData} onRemove={onRemoveFile} />
<PasswordField value={password} onChange={onChangePassword} /> <PasswordField value={password} onChange={onChangePassword} />
<SharedLinkField value={sharedLink} onChange={onChangeSharedLink} />
<div className="flex space-x-4"> <div className="flex space-x-4">
<CancelButton onClick={onCancel} /> <CancelButton onClick={onCancel} />
<StartButton onClick={onStart} /> <StartButton onClick={onStart} />
@ -92,13 +109,17 @@ function ConfirmUploadState({
function UploadingState({ function UploadingState({
uploadedFiles, uploadedFiles,
password, password,
sharedLink,
onStop, onStop,
}: { }: {
uploadedFiles: UploadedFile[] uploadedFiles: UploadedFile[]
password: string password: string
sharedLink: string
onStop: () => void onStop: () => void
}): JSX.Element { }): JSX.Element {
const fileListData = useUploaderFileListData(uploadedFiles) const fileListData = useUploaderFileListData(uploadedFiles)
const sharedSlug = extractSlugFromLink(sharedLink)
return ( return (
<PageWrapper> <PageWrapper>
<TitleText> <TitleText>
@ -109,7 +130,7 @@ function UploadingState({
</SubtitleText> </SubtitleText>
<UploadFileList files={fileListData} /> <UploadFileList files={fileListData} />
<WebRTCPeerProvider> <WebRTCPeerProvider>
<Uploader files={uploadedFiles} password={password} onStop={onStop} /> <Uploader files={uploadedFiles} password={password} sharedSlug={sharedSlug} onStop={onStop} />
</WebRTCPeerProvider> </WebRTCPeerProvider>
</PageWrapper> </PageWrapper>
) )
@ -118,6 +139,7 @@ function UploadingState({
export default function UploadPage(): JSX.Element { export default function UploadPage(): JSX.Element {
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]) const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [sharedLink, setSharedLink] = useState('')
const [uploading, setUploading] = useState(false) const [uploading, setUploading] = useState(false)
const handleDrop = useCallback((files: UploadedFile[]): void => { const handleDrop = useCallback((files: UploadedFile[]): void => {
@ -128,6 +150,10 @@ export default function UploadPage(): JSX.Element {
setPassword(pw) setPassword(pw)
}, []) }, [])
const handleChangeSharedLink = useCallback((link: string) => {
setSharedLink(link)
}, [])
const handleStart = useCallback(() => { const handleStart = useCallback(() => {
setUploading(true) setUploading(true)
}, []) }, [])
@ -158,7 +184,9 @@ export default function UploadPage(): JSX.Element {
<ConfirmUploadState <ConfirmUploadState
uploadedFiles={uploadedFiles} uploadedFiles={uploadedFiles}
password={password} password={password}
sharedLink={sharedLink}
onChangePassword={handleChangePassword} onChangePassword={handleChangePassword}
onChangeSharedLink={handleChangeSharedLink}
onCancel={handleCancel} onCancel={handleCancel}
onStart={handleStart} onStart={handleStart}
onRemoveFile={handleRemoveFile} onRemoveFile={handleRemoveFile}
@ -171,7 +199,8 @@ export default function UploadPage(): JSX.Element {
<UploadingState <UploadingState
uploadedFiles={uploadedFiles} uploadedFiles={uploadedFiles}
password={password} password={password}
sharedLink={sharedLink}
onStop={handleStop} onStop={handleStop}
/> />
) )
} }

@ -10,6 +10,8 @@ export type Channel = {
longSlug: string longSlug: string
shortSlug: string shortSlug: string
uploaderPeerID: string uploaderPeerID: string
sharedSlug?: string
additionalUploaders?: string[]
} }
const ChannelSchema = z.object({ const ChannelSchema = z.object({
@ -17,10 +19,12 @@ const ChannelSchema = z.object({
longSlug: z.string(), longSlug: z.string(),
shortSlug: z.string(), shortSlug: z.string(),
uploaderPeerID: z.string(), uploaderPeerID: z.string(),
sharedSlug: z.string().optional(),
additionalUploaders: z.array(z.string()).optional()
}) })
export interface ChannelRepo { export interface ChannelRepo {
createChannel(uploaderPeerID: string, ttl?: number): Promise<Channel> createChannel(uploaderPeerID: string, ttl?: number, sharedSlug?: string): Promise<Channel>
fetchChannel(slug: string): Promise<Channel | null> fetchChannel(slug: string): Promise<Channel | null>
renewChannel(slug: string, secret: string, ttl?: number): Promise<boolean> renewChannel(slug: string, secret: string, ttl?: number): Promise<boolean>
destroyChannel(slug: string): Promise<void> destroyChannel(slug: string): Promise<void>
@ -85,13 +89,11 @@ export class MemoryChannelRepo implements ChannelRepo {
private timeouts: Map<string, NodeJS.Timeout> = new Map() private timeouts: Map<string, NodeJS.Timeout> = new Map()
private setChannelTimeout(slug: string, ttl: number) { private setChannelTimeout(slug: string, ttl: number) {
// Clear any existing timeout
const existingTimeout = this.timeouts.get(slug) const existingTimeout = this.timeouts.get(slug)
if (existingTimeout) { if (existingTimeout) {
clearTimeout(existingTimeout) clearTimeout(existingTimeout)
} }
// Set new timeout to remove channel when expired
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
this.channels.delete(slug) this.channels.delete(slug)
this.timeouts.delete(slug) this.timeouts.delete(slug)
@ -103,19 +105,61 @@ export class MemoryChannelRepo implements ChannelRepo {
async createChannel( async createChannel(
uploaderPeerID: string, uploaderPeerID: string,
ttl: number = config.channel.ttl, ttl: number = config.channel.ttl,
sharedSlug?: string,
): Promise<Channel> { ): Promise<Channel> {
const shortSlug = await generateShortSlugUntilUnique(async (key) => const shortSlug = await generateShortSlugUntilUnique(async (key) =>
this.channels.has(key), this.channels.has(key),
) )
const longSlug = await generateLongSlugUntilUnique(async (key) => const longSlug = await generateLongSlugUntilUnique(async (key) =>
this.channels.has(key), this.channels.has(key),
) )
if (sharedSlug) {
const sharedKey = getLongSlugKey(sharedSlug)
const existingStoredChannel = this.channels.get(sharedKey)
if (existingStoredChannel) {
const updatedChannel: Channel = {
secret: crypto.randomUUID(),
longSlug,
shortSlug,
uploaderPeerID,
sharedSlug
}
const expiresAt = Date.now() + ttl * 1000
const storedChannel = { channel: updatedChannel, expiresAt }
const shortKey = getShortSlugKey(shortSlug)
const longKey = getLongSlugKey(longSlug)
this.channels.set(shortKey, storedChannel)
this.channels.set(longKey, storedChannel)
const existingChannel = {...existingStoredChannel.channel}
existingChannel.additionalUploaders = [
...(existingChannel.additionalUploaders || []),
uploaderPeerID
]
this.channels.set(sharedKey, {
channel: existingChannel,
expiresAt: expiresAt
})
this.setChannelTimeout(shortKey, ttl)
this.setChannelTimeout(longKey, ttl)
this.setChannelTimeout(sharedKey, ttl)
return updatedChannel
}
}
const channel: Channel = { const channel: Channel = {
secret: crypto.randomUUID(), secret: crypto.randomUUID(),
longSlug, longSlug,
shortSlug, shortSlug,
uploaderPeerID, uploaderPeerID,
sharedSlug
} }
const expiresAt = Date.now() + ttl * 1000 const expiresAt = Date.now() + ttl * 1000
@ -127,6 +171,12 @@ export class MemoryChannelRepo implements ChannelRepo {
this.channels.set(shortKey, storedChannel) this.channels.set(shortKey, storedChannel)
this.channels.set(longKey, storedChannel) this.channels.set(longKey, storedChannel)
if (sharedSlug) {
const sharedKey = getLongSlugKey(sharedSlug)
this.channels.set(sharedKey, storedChannel)
this.setChannelTimeout(sharedKey, ttl)
}
this.setChannelTimeout(shortKey, ttl) this.setChannelTimeout(shortKey, ttl)
this.setChannelTimeout(longKey, ttl) this.setChannelTimeout(longKey, ttl)
@ -175,6 +225,12 @@ export class MemoryChannelRepo implements ChannelRepo {
this.channels.set(longKey, storedChannel) this.channels.set(longKey, storedChannel)
this.channels.set(shortKey, storedChannel) this.channels.set(shortKey, storedChannel)
if (channel.sharedSlug) {
const sharedKey = getLongSlugKey(channel.sharedSlug)
this.channels.set(sharedKey, storedChannel)
this.setChannelTimeout(sharedKey, ttl)
}
this.setChannelTimeout(shortKey, ttl) this.setChannelTimeout(shortKey, ttl)
this.setChannelTimeout(longKey, ttl) this.setChannelTimeout(longKey, ttl)
@ -190,7 +246,6 @@ export class MemoryChannelRepo implements ChannelRepo {
const shortKey = getShortSlugKey(channel.shortSlug) const shortKey = getShortSlugKey(channel.shortSlug)
const longKey = getLongSlugKey(channel.longSlug) const longKey = getLongSlugKey(channel.longSlug)
// Clear timeouts
const shortTimeout = this.timeouts.get(shortKey) const shortTimeout = this.timeouts.get(shortKey)
if (shortTimeout) { if (shortTimeout) {
clearTimeout(shortTimeout) clearTimeout(shortTimeout)
@ -203,6 +258,16 @@ export class MemoryChannelRepo implements ChannelRepo {
this.timeouts.delete(longKey) this.timeouts.delete(longKey)
} }
if (channel.sharedSlug) {
const sharedKey = getLongSlugKey(channel.sharedSlug)
const sharedTimeout = this.timeouts.get(sharedKey)
if (sharedTimeout) {
clearTimeout(sharedTimeout)
this.timeouts.delete(sharedKey)
}
this.channels.delete(sharedKey)
}
this.channels.delete(longKey) this.channels.delete(longKey)
this.channels.delete(shortKey) this.channels.delete(shortKey)
} }
@ -218,25 +283,63 @@ export class RedisChannelRepo implements ChannelRepo {
async createChannel( async createChannel(
uploaderPeerID: string, uploaderPeerID: string,
ttl: number = config.channel.ttl, ttl: number = config.channel.ttl,
sharedSlug?: string,
): Promise<Channel> { ): Promise<Channel> {
const shortSlug = await generateShortSlugUntilUnique( const shortSlug = await generateShortSlugUntilUnique(
async (key) => (await this.client.get(key)) !== null, async (key) => (await this.client.get(key)) !== null,
) )
const longSlug = await generateLongSlugUntilUnique( const longSlug = await generateLongSlugUntilUnique(
async (key) => (await this.client.get(key)) !== null, async (key) => (await this.client.get(key)) !== null,
) )
if (sharedSlug) {
const existingChannelStr = await this.client.get(getLongSlugKey(sharedSlug))
if (existingChannelStr) {
const existingChannel = deserializeChannel(existingChannelStr)
const updatedChannel: Channel = {
secret: crypto.randomUUID(),
longSlug,
shortSlug,
uploaderPeerID,
sharedSlug
}
const channelStr = serializeChannel(updatedChannel)
await this.client.setex(getLongSlugKey(longSlug), ttl, channelStr)
await this.client.setex(getShortSlugKey(shortSlug), ttl, channelStr)
const updatedSharedChannel = {
...existingChannel,
additionalUploaders: [
...(existingChannel.additionalUploaders || []),
uploaderPeerID
]
}
const updatedSharedStr = serializeChannel(updatedSharedChannel)
await this.client.setex(getLongSlugKey(sharedSlug), ttl, updatedSharedStr)
return updatedChannel
}
}
const channel: Channel = { const channel: Channel = {
secret: crypto.randomUUID(), secret: crypto.randomUUID(),
longSlug, longSlug,
shortSlug, shortSlug,
uploaderPeerID, uploaderPeerID,
sharedSlug
} }
const channelStr = serializeChannel(channel) const channelStr = serializeChannel(channel)
await this.client.setex(getLongSlugKey(longSlug), ttl, channelStr) await this.client.setex(getLongSlugKey(longSlug), ttl, channelStr)
await this.client.setex(getShortSlugKey(shortSlug), ttl, channelStr) await this.client.setex(getShortSlugKey(shortSlug), ttl, channelStr)
if (sharedSlug) {
await this.client.setex(getLongSlugKey(sharedSlug), ttl, channelStr)
}
return channel return channel
} }
@ -270,6 +373,10 @@ export class RedisChannelRepo implements ChannelRepo {
await this.client.expire(getLongSlugKey(channel.longSlug), ttl) await this.client.expire(getLongSlugKey(channel.longSlug), ttl)
await this.client.expire(getShortSlugKey(channel.shortSlug), ttl) await this.client.expire(getShortSlugKey(channel.shortSlug), ttl)
if (channel.sharedSlug) {
await this.client.expire(getLongSlugKey(channel.sharedSlug), ttl)
}
return true return true
} }
@ -281,6 +388,10 @@ export class RedisChannelRepo implements ChannelRepo {
await this.client.del(getLongSlugKey(channel.longSlug)) await this.client.del(getLongSlugKey(channel.longSlug))
await this.client.del(getShortSlugKey(channel.shortSlug)) await this.client.del(getShortSlugKey(channel.shortSlug))
if (channel.sharedSlug) {
await this.client.del(getLongSlugKey(channel.sharedSlug))
}
} }
} }
@ -297,4 +408,4 @@ export function getOrCreateChannelRepo(): ChannelRepo {
} }
} }
return _channelRepo return _channelRepo
} }

@ -0,0 +1,47 @@
'use client'
import React, { useState, JSX, useEffect } from 'react'
import Downloader from './Downloader'
export default function MultiDownloader({
primaryUploaderID,
additionalUploaders,
}: {
primaryUploaderID: string
additionalUploaders: string[]
}): JSX.Element {
const [selectedUploader, setSelectedUploader] = useState<string>(primaryUploaderID)
const [key, setKey] = useState<number>(0)
const allUploaders = [primaryUploaderID, ...additionalUploaders]
useEffect(() => {
setKey(prevKey => prevKey + 1)
}, [selectedUploader])
return (
<div className="w-full flex flex-col items-center">
<div className="w-full mb-4">
<h3 className="text-center mb-2 text-stone-700 dark:text-stone-300">
Multiple uploaders available for this shared link:
</h3>
<div className="flex flex-wrap justify-center gap-2 mb-4">
{allUploaders.map((uploader, i) => (
<button
key={uploader}
onClick={() => setSelectedUploader(uploader)}
className={`px-3 py-1 rounded transition-colors duration-200 ${
selectedUploader === uploader
? 'bg-green-500 text-white'
: 'bg-stone-200 dark:bg-stone-700 text-stone-800 dark:text-stone-200 hover:bg-stone-300 dark:hover:bg-stone-600'
}`}
>
Uploader {i + 1}
</button>
))}
</div>
</div>
<Downloader key={key} uploaderPeerID={selectedUploader} />
</div>
)
}

@ -0,0 +1,39 @@
'use client'
import React, { JSX, useCallback } from 'react'
import InputLabel from './InputLabel'
export default function SharedLinkField({
value,
onChange,
}: {
value: string
onChange: (v: string) => void
}): JSX.Element {
const handleChange = useCallback(
function (e: React.ChangeEvent<HTMLInputElement>): void {
onChange(e.target.value)
},
[onChange],
)
return (
<div className="flex flex-col w-full">
<InputLabel
tooltip="Enter a shared link to collaborate with other uploaders. If this is filled, others with the same link will be able to provide the same files to downloaders. Leave empty to create a new upload."
>
Shared Link (optional)
</InputLabel>
<input
type="text"
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 border-stone-300 dark:border-stone-600 bg-white dark:bg-stone-800 text-stone-900 dark:text-stone-100"
placeholder="Paste a shared link to collaborate with others..."
value={value}
onChange={handleChange}
/>
<p className="mt-1 text-xs text-stone-500 dark:text-stone-400">
You can paste either a full URL or just the slug. When shared, multiple uploaders can provide the same files, making downloads more reliable.
</p>
</div>
)
}

@ -18,15 +18,17 @@ const QR_CODE_SIZE = 128
export default function Uploader({ export default function Uploader({
files, files,
password, password,
sharedSlug,
onStop, onStop,
}: { }: {
files: UploadedFile[] files: UploadedFile[]
password: string password: string
sharedSlug?: string
onStop: () => void onStop: () => void
}): JSX.Element { }): JSX.Element {
const { peer, stop } = useWebRTCPeer() const { peer, stop } = useWebRTCPeer()
const { isLoading, error, longSlug, shortSlug, longURL, shortURL } = const { isLoading, error, longSlug, shortSlug, longURL, shortURL, sharedURL } =
useUploaderChannel(peer.id) useUploaderChannel(peer.id, 60000, sharedSlug)
const connections = useUploaderConnections(peer, files, password) const connections = useUploaderConnections(peer, files, password)
const handleStop = useCallback(() => { const handleStop = useCallback(() => {
@ -59,6 +61,16 @@ export default function Uploader({
<div className="flex-auto flex flex-col justify-center space-y-2"> <div className="flex-auto flex flex-col justify-center space-y-2">
<CopyableInput label="Long URL" value={longURL ?? ''} /> <CopyableInput label="Long URL" value={longURL ?? ''} />
<CopyableInput label="Short URL" value={shortURL ?? ''} /> <CopyableInput label="Short URL" value={shortURL ?? ''} />
{sharedURL && (
<>
<CopyableInput label="Shared URL" value={sharedURL} />
{sharedSlug && (
<div className="text-xs text-green-600 dark:text-green-400">
This upload is part of a collaborative shared link. Multiple uploaders can serve the same files.
</div>
)}
</>
)}
</div> </div>
</div> </div>
<div className="mt-6 pt-4 border-t border-stone-200 dark:border-stone-700 w-full"> <div className="mt-6 pt-4 border-t border-stone-200 dark:border-stone-700 w-full">
@ -74,4 +86,4 @@ export default function Uploader({
</div> </div>
</> </>
) )
} }

@ -13,6 +13,7 @@ function generateURL(slug: string): string {
export function useUploaderChannel( export function useUploaderChannel(
uploaderPeerID: string, uploaderPeerID: string,
renewInterval = 60_000, renewInterval = 60_000,
sharedSlug?: string
): { ): {
isLoading: boolean isLoading: boolean
error: Error | null error: Error | null
@ -20,18 +21,22 @@ export function useUploaderChannel(
shortSlug: string | undefined shortSlug: string | undefined
longURL: string | undefined longURL: string | undefined
shortURL: string | undefined shortURL: string | undefined
sharedSlug: string | undefined
sharedURL: string | undefined
additionalUploaders: string[] | undefined
} { } {
const { isLoading, error, data } = useQuery({ const { isLoading, error, data } = useQuery({
queryKey: ['uploaderChannel', uploaderPeerID], queryKey: ['uploaderChannel', uploaderPeerID, sharedSlug],
queryFn: async () => { queryFn: async () => {
console.log( console.log(
'[UploaderChannel] creating new channel for peer', '[UploaderChannel] creating new channel for peer',
uploaderPeerID, uploaderPeerID,
sharedSlug ? `with shared slug ${sharedSlug}` : ''
) )
const response = await fetch('/api/create', { const response = await fetch('/api/create', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ uploaderPeerID }), body: JSON.stringify({ uploaderPeerID, sharedSlug }),
}) })
if (!response.ok) { if (!response.ok) {
console.error( console.error(
@ -44,6 +49,8 @@ export function useUploaderChannel(
console.log('[UploaderChannel] channel created successfully:', { console.log('[UploaderChannel] channel created successfully:', {
longSlug: data.longSlug, longSlug: data.longSlug,
shortSlug: data.shortSlug, shortSlug: data.shortSlug,
sharedSlug: data.sharedSlug,
additionalUploaders: data.additionalUploaders
}) })
return data return data
}, },
@ -56,8 +63,11 @@ export function useUploaderChannel(
const secret = data?.secret const secret = data?.secret
const longSlug = data?.longSlug const longSlug = data?.longSlug
const shortSlug = data?.shortSlug const shortSlug = data?.shortSlug
const returnedSharedSlug = data?.sharedSlug
const longURL = longSlug ? generateURL(longSlug) : undefined const longURL = longSlug ? generateURL(longSlug) : undefined
const shortURL = shortSlug ? generateURL(shortSlug) : undefined const shortURL = shortSlug ? generateURL(shortSlug) : undefined
const sharedURL = returnedSharedSlug ? generateURL(returnedSharedSlug) : undefined
const additionalUploaders = data?.additionalUploaders
const renewMutation = useMutation({ const renewMutation = useMutation({
mutationFn: async ({ secret: s }: { secret: string }) => { mutationFn: async ({ secret: s }: { secret: string }) => {
@ -130,5 +140,8 @@ export function useUploaderChannel(
shortSlug, shortSlug,
longURL, longURL,
shortURL, shortURL,
sharedSlug: returnedSharedSlug,
sharedURL,
additionalUploaders
} }
} }
Loading…
Cancel
Save