Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,14 @@ enum ProviderTabs {
All = 'All',
IPFS = 'IPFS',
Arweave = 'Arweave',
Load = 'Load Network',
}

const TabToProviderMap: Record<ProviderTabs, Provider | null> = {
[ProviderTabs.All]: null,
[ProviderTabs.IPFS]: Provider.IPFS,
[ProviderTabs.Arweave]: Provider.Arweave,
[ProviderTabs.Load]: Provider.Load,
}

const providers = getEnumAsArray(ProviderTabs)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// todo:the issue is potentially the file size limit

import { Icons } from '@masknet/icons'
import { UploadDropArea } from '@masknet/shared'
import { makeStyles } from '@masknet/theme'
import { Checkbox, FormControlLabel, Radio, Typography } from '@mui/material'
import { useCallback, useMemo, useState, type ReactNode } from 'react'
import { MAX_FILE_SIZE } from '../../constants.js'
import { MAX_FILE_SIZE, MAX_FILE_SIZE_LOAD } from '../../constants.js'
import { downloadFile } from '../../helpers.js'
import { Provider } from '../../types.js'
import { useFileManagement } from '../contexts/index.js'
Expand Down Expand Up @@ -79,6 +81,8 @@ export function UploadFile() {
const [provider, setProvider] = useState<Provider>(Provider.Arweave)
const { recentFiles, uploadingFiles, uploadFile, attachToPost } = useFileManagement()

const FILE_SIZE = provider === Provider.Load ? MAX_FILE_SIZE_LOAD : MAX_FILE_SIZE

const files = useMemo(() => {
return [...uploadingFiles, ...recentFiles]
}, [uploadingFiles, recentFiles])
Expand All @@ -92,6 +96,10 @@ export function UploadFile() {
provider: Provider.IPFS,
name: <Trans>IPFS</Trans>,
},
{
provider: Provider.Load,
name: <Trans>Load Network</Trans>,
},
]

const onSelectFile = useCallback(
Expand All @@ -103,7 +111,7 @@ export function UploadFile() {

return (
<section className={classes.container}>
<UploadDropArea className={classes.uploadArea} maxFileSize={MAX_FILE_SIZE} onSelectFile={onSelectFile} />
<UploadDropArea className={classes.uploadArea} maxFileSize={FILE_SIZE} onSelectFile={onSelectFile} />
<div className={classes.options}>
{providers.map((config: ProviderConfig) => (
<FormControlLabel
Expand Down
116 changes: 116 additions & 0 deletions packages/plugins/FileService/src/Worker/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { isEmpty } from 'lodash-es'
import { Attachment } from '@dimensiondev/common-protocols'
import { encodeText } from '@masknet/kit'
import { LANDING_PAGE, Provider } from '../constants.js'
import type { ProviderAgent, LandingPageMetadata, AttachmentOptions } from '../types.js'
import { makeFileKeySigned } from '../helpers.js'

const LOAD_GATEWAY_URL = 'https://load0.network/resolve'
const LOAD_UPLOAD_ENDPOINT = 'https://load0.network/upload'
const API_KEY = 'load_acc_ep4bep0uGlmUXMu46BxM0uWXKsqL5M5w' // move to env

class LoadAgent implements ProviderAgent {
static providerName = 'Load Network'
private uploadController?: AbortController

init() {
this.uploadController = new AbortController()
}

async makeAttachment(options: AttachmentOptions) {
this.init()
const passphrase = options.key ? encodeText(options.key) : undefined
const encoded = await Attachment.encode(passphrase, {
block: options.block,
mime: isEmpty(options.type) ? 'application/octet-stream' : options.type,
metadata: null,
})

const effectiveType = isEmpty(options.type) ? 'application/octet-stream' : options.type
const effectiveName = options.name || 'unnamed_file'
const payloadTxID = await this.makePayload(encoded, effectiveType, effectiveName)
return payloadTxID
}

async *upload(id: string) {
try {
// Since we're using optimistic upload, we can yield progress immediately
// The actual upload to Load Network happens in the background
yield 50
yield 100
} catch (error) {
console.error('Upload progress tracking failed:', error)
throw error
} finally {
if (this.uploadController) {
this.uploadController.abort()
this.uploadController = undefined
}
}
}

async uploadLandingPage(metadata: LandingPageMetadata) {
this.init()
const linkPrefix = LOAD_GATEWAY_URL
const encodedMetadata = JSON.stringify({
name: metadata.name,
size: metadata.size,
provider: Provider.Load,
link: `${linkPrefix}/${metadata.txId}`,
signed: await makeFileKeySigned(metadata.key),
createdAt: new Date().toISOString(),
})
const response = await fetch(LANDING_PAGE)
const text = await response.text()
const replaced = text
.replace('Arweave', LoadAgent.providerName)
.replace('Over Arweave', `Over ${LoadAgent.providerName}`)
.replace('__METADATA__', encodedMetadata)

const data = encodeText(replaced)

const landingPageTxId = await this.makePayload(data, 'text/html', `${metadata.name}-landing.html`)

return landingPageTxId
}

async makePayload(data: Uint8Array, type: string, fileName: string = 'file.dat') {
this.init()

try {
const blob = new Blob([data], { type })
const headers = {
'Content-Type': type,
'Filename': fileName,
'App-Name': 'Maskbook',
'X-Load-Authorization': API_KEY
}

const response = await fetch(LOAD_UPLOAD_ENDPOINT, {
method: 'POST',
headers,
body: blob,
signal: this.uploadController?.signal
})

if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`)
}

const { optimistic_hash } = await response.json()
return optimistic_hash
} catch (error) {
const errorMessage = `Load Network upload failed: ${error instanceof Error ? error.message : String(error)}`
console.error('Load Network detailed error:', errorMessage)

const enhancedError = new Error(errorMessage)
if (error instanceof Error && error.stack) {
enhancedError.stack = error.stack
}

throw enhancedError
}
}
}

export default new LoadAgent()
2 changes: 2 additions & 0 deletions packages/plugins/FileService/src/Worker/service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { type AttachmentOptions, type LandingPageMetadata, Provider, type ProviderAgent } from '../types.js'
import arweave from './arweave.js'
import ipfs from './ipfs.js'
import load from './load.js'

const allProviders: Record<Provider, ProviderAgent> = {
[Provider.Arweave]: arweave,
[Provider.IPFS]: ipfs,
[Provider.Load]: load,
}

export async function makeAttachment(provider: Provider, options: AttachmentOptions) {
Expand Down
2 changes: 2 additions & 0 deletions packages/plugins/FileService/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const META_KEY_2 = 'com.maskbook.fileservice:2'
export const META_KEY_3 = 'com.maskbook.fileservice:3'

export const MAX_FILE_SIZE = 10 * 1000 * 1000
export const MAX_FILE_SIZE_LOAD = 10 * 1024 * 1024

export const LANDING_PAGE = 'https://files.r2d2.to/partner/arweave/landing-page.html'
export const RECOVERY_PAGE = 'https://fileservice.r2d2.to/recover'
Expand All @@ -19,4 +20,5 @@ export const enum RoutePaths {
export const enum Provider {
IPFS = 'ipfs',
Arweave = 'arweave',
Load = 'load',
}
1 change: 1 addition & 0 deletions packages/plugins/FileService/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const resolveGatewayAPI = createLookupTableResolver<Provider, string>(
{
[Provider.Arweave]: 'https://arweave.net',
[Provider.IPFS]: 'https://mask.infura-ipfs.io/ipfs',
[Provider.Load]: 'https://load0.network/resolve',
},
() => 'Unknown provider',
)
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/FileService/src/schema-v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"provider": {
"type": "string",
"enum": ["arweave", "ipfs"],
"enum": ["arweave", "ipfs", "load"],
"title": "provider"
},
"id": {
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/FileService/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FileFrameProps } from '@masknet/shared'
export enum Provider {
Arweave = 'arweave',
IPFS = 'ipfs',
Load = 'load',
}

export interface LandingPageMetadata {
Expand Down
29 changes: 17 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.