diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index 18f81567dd5f..50ab077a24ae 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -20,6 +20,8 @@ const approvedList = new Map([ ['html-parse-stringify2', 'github:locize/html-parse-stringify2'], /* cspell:disable-next-line */ ['async-eventemitter', 'github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c'], + // Introduced by ipfs-utils + ['node-fetch', 'https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz'], ]) /** diff --git a/cspell.json b/cspell.json index 5bdfcc6afe7e..ba8f162b1d94 100644 --- a/cspell.json +++ b/cspell.json @@ -76,6 +76,7 @@ "elec", "encryptedmsg", "endregion", + "ethersphere", "ethersproject", "eustatius", "everytime", @@ -106,6 +107,7 @@ "imtokenv2", "investables", "ipfs", + "IPFSHTTP", "ipfsurl", "ittr", "jailbroken", diff --git a/packages/plugins/FileService/package.json b/packages/plugins/FileService/package.json index f584728fc976..dc1d7aeae8cb 100644 --- a/packages/plugins/FileService/package.json +++ b/packages/plugins/FileService/package.json @@ -5,6 +5,7 @@ "types": "./dist/index.d.ts", "dependencies": { "@dimensiondev/common-protocols": "1.6.0-20210723072221-8f46a92", + "@ethersphere/bee-js": "^3.3.1", "@masknet/icons": "workspace:*", "@masknet/plugin-infra": "workspace:*", "@masknet/plugin-wallet": "workspace:*", @@ -12,7 +13,10 @@ "@masknet/shared-base": "workspace:*", "@masknet/theme": "workspace:*", "@msgpack/msgpack": "^2.7.2", + "@masknet/web3-kit": "workspace:*", "arweave": "^1.10.3", + "ipfs-http-client": "^54.0.2", + "urlcat": "^2.0.4", "date-fns": "^2.28.0", "react-feather": "^2.0.9", "react-router-dom": "5", diff --git a/packages/plugins/FileService/src/SNSAdaptor/MainDialog.tsx b/packages/plugins/FileService/src/SNSAdaptor/MainDialog.tsx index c416644fe167..54b3ee036f65 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/MainDialog.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/MainDialog.tsx @@ -8,7 +8,7 @@ import { WalletMessages } from '@masknet/plugin-wallet' import { Entry } from './components' import { META_KEY_2 } from '../constants' import { Exchange } from './hooks/Exchange' -import type { FileInfo } from '../types' +import type { FileInfo, DialogCloseCallback } from '../types' import { useCompositionContext } from '@masknet/plugin-infra' interface Props { @@ -62,13 +62,29 @@ const FileServiceDialog: React.FC = (props) => { closeWalletStatusDialog() props.onClose() } + + let onDialogCloseCallback: DialogCloseCallback | null + const callDialogClose = () => { + try { + onDialogCloseCallback?.() + } catch (error) {} + onDialogCloseCallback = null + } + const onDecline = () => { + if (onDialogCloseCallback) { + callDialogClose() + return + } if (!uploading) { props.onClose() return } showSnackbar(t.uploading_on_cancel()) } + const onDialogClose = (cb: DialogCloseCallback) => { + onDialogCloseCallback = cb + } return ( = (props) => { title={t.__display_name()} onClose={onDecline}> - + diff --git a/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx b/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx index 295211beb8aa..808bb952cbb7 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx @@ -5,6 +5,8 @@ import { DownloadCloud, File } from 'react-feather' import { useI18N } from '../locales/i18n_generated' import { CopyableCode } from './components/Copyable' import type { FileInfo } from '../types' +import { resolveGatewayAPI } from '../helpers' +import urlcat from 'urlcat' const useStyles = makeStyles()((theme) => ({ root: { @@ -55,7 +57,10 @@ export function Preview({ info }: { info: FileInfo }) { {t.unencrypted()} ) - const link = `https://arweave.net/${info.landingTxID}` + + const linkPrefix = resolveGatewayAPI(info.provider) + const link = urlcat(linkPrefix, '/:txId', { txId: info.landingTxID }) + const onClick = (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() diff --git a/packages/plugins/FileService/src/SNSAdaptor/components/Upload.tsx b/packages/plugins/FileService/src/SNSAdaptor/components/Upload.tsx index 744ff4450936..f53f99370a61 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/components/Upload.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/components/Upload.tsx @@ -1,6 +1,6 @@ import { Attachment } from '@dimensiondev/common-protocols' import { blobToArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' -import { Checkbox, FormControlLabel, Link, Typography } from '@mui/material' +import { Checkbox, Radio, FormControlLabel, Link, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' import { isNil } from 'lodash-unified' import { useState } from 'react' @@ -8,10 +8,12 @@ import { useHistory } from 'react-router-dom' import { useAsync } from 'react-use' import { useI18N, Translate } from '../../locales/i18n_generated' import { makeFileKey } from '../../file-key' +import type { ProviderConfig } from '../../types' import { FileRouter, MAX_FILE_SIZE } from '../../constants' import { PluginFileServiceRPC } from '../../Worker/rpc' import { RecentFiles } from './RecentFiles' import { UploadDropArea } from './UploadDropArea' +import { Provider } from '../../types' const useStyles = makeStyles()((theme) => ({ container: { @@ -57,7 +59,23 @@ export const Upload: React.FC = () => { const history = useHistory() const [encrypted, setEncrypted] = useState(true) const [useCDN, setUseCDN] = useState(false) + const [provider, setProvider] = useState(Provider.arweave) const recent = useAsync(() => PluginFileServiceRPC.getRecentFiles(), []) + const allProviders: ProviderConfig[] = [ + { + provider: Provider.arweave, + name: t.provider_arweave(), + }, + { + provider: Provider.ipfs, + name: t.provider_ipfs(), + }, + { + provider: Provider.swarm, + name: t.provider_swarm(), + }, + ] + const onFile = async (file: File) => { let key: string | undefined = undefined if (encrypted) { @@ -75,11 +93,39 @@ export const Upload: React.FC = () => { block, checksum, useCDN, + provider, }) } else { history.replace(FileRouter.uploaded, item) } } + + const allProviderOptions = allProviders.map((config: ProviderConfig) => ( + setProvider(config.provider)} + /> + } + className={classes.encrypted} + label={config.name} + /> + )) + + const cdnButton = + provider === Provider.arweave ? ( + setUseCDN(event.target.checked)} /> + } + className={classes.usedCDN} + label={t.use_cdn()} + /> + ) : null + return (
@@ -98,18 +144,9 @@ export const Upload: React.FC = () => { className={classes.encrypted} label={t.on_encrypt_it()} /> - setUseCDN(event.target.checked)} - /> - } - className={classes.usedCDN} - label={t.use_cdn()} - /> + {cdnButton}
+
{allProviderOptions}
({ here: { fontSize: 12, lineHeight: 2.5, - color: theme.palette.grey[100], + color: theme.palette.mode === 'dark' ? theme.palette.grey[100] : theme.palette.grey[500], userSelect: 'none', }, hint: { diff --git a/packages/plugins/FileService/src/SNSAdaptor/components/Uploaded.tsx b/packages/plugins/FileService/src/SNSAdaptor/components/Uploaded.tsx index 3e12c6caeaec..b0adf7450510 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/components/Uploaded.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/components/Uploaded.tsx @@ -10,6 +10,8 @@ import { useI18N } from '../../locales/i18n_generated' import { FileRouter } from '../../constants' import type { FileInfo } from '../../types' import { FileName } from './FileName' +import { resolveGatewayAPI } from '../../helpers' +import urlcat from 'urlcat' const useStyles = makeStyles()({ container: { @@ -41,6 +43,14 @@ const useStyles = makeStyles()({ flexDirection: 'column', justifyContent: 'center', }, + change: { + fontSize: 14, + margin: '0 auto', + display: 'flex', + padding: '0 60px', + flexDirection: 'column', + justifyContent: 'center', + }, info: { margin: 0, }, @@ -50,7 +60,7 @@ export const Uploaded: React.FC = () => { const t = useI18N() const { classes } = useStyles() const history = useHistory() - const { onInsert } = useExchange() + const { onInsert, onDialogClose } = useExchange() const { state } = useLocation() useEffect(() => { onInsert(state) @@ -59,6 +69,9 @@ export const Uploaded: React.FC = () => { onInsert(null) history.replace(FileRouter.upload) } + + // return upload route + onDialogClose(onBack) const onPreview = (event: React.MouseEvent) => { // ! THIS METHOD IS ONLY IN THE DEBUGGER ! // ! Trigger: [Shift Key] + [Click] ! @@ -66,7 +79,9 @@ export const Uploaded: React.FC = () => { if (!event.shiftKey) { return } - const link = `https://arweave.net/${state.landingTxID}` + + const linkPrefix = resolveGatewayAPI(state.provider) + const link = urlcat(linkPrefix, '/:txId', { txId: state.landingTxID }) open(state.key ? `${link}#${state.key}` : link) } return ( @@ -82,10 +97,10 @@ export const Uploaded: React.FC = () => { {' '} {formatDateTime(state.createdAt, 'yyyy-MM-dd HH:mm:ss')}

-
+ ) diff --git a/packages/plugins/FileService/src/SNSAdaptor/components/Uploading.tsx b/packages/plugins/FileService/src/SNSAdaptor/components/Uploading.tsx index a9303af02391..bead54c7fea1 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/components/Uploading.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/components/Uploading.tsx @@ -7,7 +7,7 @@ import { useAsync } from 'react-use' import { useI18N } from '../../locales/i18n_generated' import { timeout } from '@masknet/shared-base' import { FileRouter } from '../../constants' -import type { FileInfo } from '../../types' +import type { FileInfo, Provider } from '../../types' import { PluginFileServiceRPC, PluginFileServiceRPCGenerator } from '../../Worker/rpc' import { useExchange } from '../hooks/Exchange' import { FileName } from './FileName' @@ -42,6 +42,7 @@ interface RouteState { block: Uint8Array checksum: string useCDN: boolean + provider: Provider } export const Uploading: React.FC = () => { @@ -58,8 +59,10 @@ export const Uploading: React.FC = () => { return () => onUploading(false) }, [onUploading]) const { error } = useAsync(async () => { + const currentProvider = state.provider as Provider const payloadTxID = await timeout( - PluginFileServiceRPC.makeAttachment({ + PluginFileServiceRPC.makeAttachment(currentProvider, { + name: state.name, key: state.key, block: state.block, type: state.type, @@ -67,11 +70,11 @@ export const Uploading: React.FC = () => { 60000, // = 1 minute ) setPreparing(false) - for await (const pctComplete of PluginFileServiceRPCGenerator.upload(payloadTxID)) { + for await (const pctComplete of PluginFileServiceRPCGenerator.upload(currentProvider, payloadTxID)) { setSendSize(state.size * (pctComplete / 100)) } const landingTxID = await timeout( - PluginFileServiceRPC.uploadLandingPage({ + PluginFileServiceRPC.uploadLandingPage(currentProvider, { name: state.name, size: state.size, txId: payloadTxID, @@ -83,9 +86,8 @@ export const Uploading: React.FC = () => { ) const item: FileInfo = { type: 'file', - provider: 'arweave', + provider: currentProvider, id: state.checksum, - name: state.name, size: state.size, createdAt: new Date(startedAt), diff --git a/packages/plugins/FileService/src/SNSAdaptor/hooks/Exchange.tsx b/packages/plugins/FileService/src/SNSAdaptor/hooks/Exchange.tsx index 7eb426a976b0..b05801493a65 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/hooks/Exchange.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/hooks/Exchange.tsx @@ -1,15 +1,17 @@ import { noop, omit } from 'lodash-unified' import { createContext, useContext } from 'react' -import type { FileInfo } from '../../types' +import type { FileInfo, DialogCloseCallback } from '../../types' export interface Props { onInsert(info: FileInfo | null): void onUploading(enabled: boolean): void + onDialogClose(cb: DialogCloseCallback): void } const Context = createContext({ onInsert: noop, onUploading: noop, + onDialogClose: noop, }) export const Exchange: React.FC = (props) => ( diff --git a/packages/plugins/FileService/src/Worker/arweave.ts b/packages/plugins/FileService/src/Worker/arweave.ts index 67f307df2a9b..0c991c29bd8f 100644 --- a/packages/plugins/FileService/src/Worker/arweave.ts +++ b/packages/plugins/FileService/src/Worker/arweave.ts @@ -1,90 +1,79 @@ import { Attachment } from '@dimensiondev/common-protocols' -import { encodeArrayBuffer, encodeText } from '@dimensiondev/kit' +import { encodeText } from '@dimensiondev/kit' import Arweave from 'arweave/web' import type Transaction from 'arweave/web/lib/transaction' -import { isEmpty, isNil } from 'lodash-unified' +import type { JWKInterface } from 'arweave/web/lib/wallet' +import { isEmpty } from 'lodash-unified' import { landing, mesonPrefix } from '../constants' import { sign } from './remote-signing' import TOKEN from './arweave-token.json' +import type { ProviderAgent, LandingPageMetadata, AttachmentOptions } from '../types' +import { makeFileKeySigned } from '../helpers' -const stage: Record = {} +class ArweaveAgent implements ProviderAgent { + instance!: Arweave + static stage: Record = {} -const instance = Arweave.init({ - host: 'arweave.net', - port: 443, - protocol: 'https', -}) - -export interface AttachmentOptions { - key?: string | null - type: string - block: Uint8Array -} - -export async function makeAttachment(options: AttachmentOptions) { - 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 transaction = await makePayload(encoded, 'application/octet-stream') - stage[transaction.id] = transaction - return transaction.id -} - -// import { ServicesWithProgress } from 'src/extension/service.ts' -// ServicesWithProgress.pluginArweaveUpload -export async function* upload(id: Transaction['id']) { - for await (const uploader of instance.transactions.upload(stage[id])) { - yield uploader.pctComplete + init() { + if (this.instance) return + this.instance = Arweave.init({ + host: 'arweave.net', + port: 443, + protocol: 'https', + }) } -} -export interface LandingPageMetadata { - key: string | null | undefined - name: string - size: number - type: string - txId: string - useCDN: boolean -} + 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 transaction = await this.makePayload(encoded, 'application/octet-stream') + ArweaveAgent.stage[transaction.id] = transaction + await this.instance.transactions.post(transaction) + return transaction.id + } -export async function uploadLandingPage(metadata: LandingPageMetadata) { - let linkPrefix = 'https://arweave.net' - if (metadata.useCDN) { - linkPrefix = mesonPrefix + async uploadLandingPage(metadata: LandingPageMetadata) { + this.init() + let linkPrefix = 'https://arweave.net' + if (metadata.useCDN) { + linkPrefix = mesonPrefix + } + const encodedMetadata = JSON.stringify({ + name: metadata.name, + size: metadata.size, + provider: 'arweave', + link: `${linkPrefix}/${metadata.txId}`, + signed: await makeFileKeySigned(metadata.key), + createdAt: new Date().toISOString(), + }) + const response = await fetch(landing) + const text = await response.text() + const replaced = text.replace('__METADATA__', encodedMetadata) + const data = encodeText(replaced) + const transaction = await this.makePayload(data, 'text/html') + await this.instance.transactions.post(transaction) + return transaction.id } - const encodedMetadata = JSON.stringify({ - name: metadata.name, - size: metadata.size, - link: `${linkPrefix}/${metadata.txId}`, - signed: await makeFileKeySigned(metadata.key), - createdAt: new Date().toISOString(), - }) - const response = await fetch(landing) - const text = await response.text() - const replaced = text.replace('__METADATA__', encodedMetadata) - const data = encodeText(replaced) - const transaction = await makePayload(data, 'text/html') - await instance.transactions.post(transaction) - return transaction.id -} -async function makePayload(data: Uint8Array, type: string) { - const transaction = await instance.createTransaction({ data }, { ...TOKEN, e: '' }) - transaction.addTag('Content-Type', type) - await sign(transaction) - return transaction -} + async *upload(id: Transaction['id']) { + this.init() + for await (const uploader of this.instance.transactions.upload(ArweaveAgent.stage[id])) { + yield uploader.pctComplete + } + } -async function makeFileKeySigned(fileKey: string | undefined | null) { - if (isNil(fileKey)) { - return null + async makePayload(data: Uint8Array, type: string) { + this.init() + const transaction = await this.instance.createTransaction({ data }, TOKEN as JWKInterface) + transaction.addTag('Content-Type', type) + await sign(transaction) + return transaction } - const encodedKey = encodeText(fileKey) - const key = await crypto.subtle.generateKey({ name: 'HMAC', hash: { name: 'SHA-256' } }, true, ['sign', 'verify']) - const exportedKey = await crypto.subtle.exportKey('raw', key) - const signed = await crypto.subtle.sign({ name: 'HMAC' }, key, encodedKey) - return [signed, exportedKey].map(encodeArrayBuffer) } + +export default new ArweaveAgent() diff --git a/packages/plugins/FileService/src/Worker/ipfs.ts b/packages/plugins/FileService/src/Worker/ipfs.ts new file mode 100644 index 000000000000..b9c3bec483e1 --- /dev/null +++ b/packages/plugins/FileService/src/Worker/ipfs.ts @@ -0,0 +1,69 @@ +import { Attachment } from '@dimensiondev/common-protocols' +import { encodeText } from '@dimensiondev/kit' +import { create, IPFSHTTPClient } from 'ipfs-http-client' +import { isEmpty } from 'lodash-unified' +import { landing } from '../constants' +import urlcat from 'urlcat' +import type { ProviderAgent, LandingPageMetadata, AttachmentOptions } from '../types' +import { makeFileKeySigned } from '../helpers' + +function createClient(): IPFSHTTPClient { + return create({ + host: 'ipfs.infura.io', + port: 5001, + protocol: 'https', + }) +} + +class IPFSAgent implements ProviderAgent { + static providerName = 'IPFS' + client!: IPFSHTTPClient + + init() { + if (this.client) return + this.client = createClient() + } + + async makeAttachment(options: AttachmentOptions) { + 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, + }) + return this.makePayload(encoded, 'application/octet-stream') + } + + // currently not native support progress track + async *upload(id: string) { + yield 100 + } + + async uploadLandingPage(metadata: LandingPageMetadata) { + const linkPrefix = 'https://ipfs.infura.io/ipfs' + const encodedMetadata = JSON.stringify({ + name: metadata.name, + size: metadata.size, + provider: 'ipfs', + link: urlcat(linkPrefix, '/:txId', { txId: metadata.txId }), + signed: await makeFileKeySigned(metadata.key), + createdAt: new Date().toISOString(), + }) + const response = await fetch(landing) + const text = await response.text() + const replaced = text + .replace('Arweave', IPFSAgent.providerName) + .replace('Over Arweave', `Over ${IPFSAgent.providerName}`) + .replace('__METADATA__', encodedMetadata) + const data = encodeText(replaced) + return this.makePayload(data, 'text/html') + } + + async makePayload(data: Uint8Array, type: string) { + this.init() + const file = await this.client.add(data) + return file.cid.toString() + } +} + +export default new IPFSAgent() diff --git a/packages/plugins/FileService/src/Worker/service.ts b/packages/plugins/FileService/src/Worker/service.ts index b5cb7f2742a2..c0806547c9f0 100644 --- a/packages/plugins/FileService/src/Worker/service.ts +++ b/packages/plugins/FileService/src/Worker/service.ts @@ -1,2 +1,26 @@ -export * from './arweave' +import { AttachmentOptions, LandingPageMetadata, Provider, ProviderAgent } from '../types' +import arweave from './arweave' +import ipfs from './ipfs' +import swarm from './swarm' + +const allProviders: Record = { + [Provider.arweave]: arweave, + [Provider.ipfs]: ipfs, + [Provider.swarm]: swarm, +} + +export async function makeAttachment(provider: Provider, options: AttachmentOptions) { + return allProviders[provider]?.makeAttachment(options) +} + +export async function* upload(provider: Provider, id: string) { + for await (const percent of allProviders[provider].upload(id)) { + yield percent + } +} + +export async function uploadLandingPage(provider: Provider, metadata: LandingPageMetadata) { + return allProviders[provider]?.uploadLandingPage(metadata) +} + export * from './database' diff --git a/packages/plugins/FileService/src/Worker/swarm.ts b/packages/plugins/FileService/src/Worker/swarm.ts new file mode 100644 index 000000000000..034e48d3275f --- /dev/null +++ b/packages/plugins/FileService/src/Worker/swarm.ts @@ -0,0 +1,84 @@ +import { Attachment } from '@dimensiondev/common-protocols' +import { encodeText } from '@dimensiondev/kit' +import { Bee, CollectionEntry } from '@ethersphere/bee-js' +import urlcat from 'urlcat' +import { isEmpty, times, sample } from 'lodash-unified' +import { landing } from '../constants' +import type { ProviderAgent, LandingPageMetadata, AttachmentOptions } from '../types' +import { makeFileKeySigned } from '../helpers' + +const POSTAGE_STAMP = '0'.repeat(64) +const BEE_HOSTS = times(10, (n) => `https://bee-${n}.gateway.ethswarm.org`) + +function createBee(): Bee { + return new Bee(sample(BEE_HOSTS)!) +} + +class SwarmAgent implements ProviderAgent { + static providerName = 'Swarm' + bee!: Bee + + init() { + if (this.bee) return + this.bee = createBee() + } + + async makePayload(data: Uint8Array, type: string, name: string) { + this.init() + const isHTML = type === 'text/html' + if (isHTML) { + const file: CollectionEntry = { + path: name, + data: data, + } + const { reference } = await this.bee.uploadCollection(POSTAGE_STAMP, [file], { + encrypt: false, + indexDocument: name, + }) + return reference + } + const { reference } = await this.bee.uploadFile(POSTAGE_STAMP, data, name, { + encrypt: false, + size: data.length, + contentType: type, + }) + return reference + } + + async makeAttachment(options: AttachmentOptions) { + 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, + }) + return this.makePayload(encoded, 'application/octet-stream', options.name) + } + + // currently not native support progress track + async *upload(id: string) { + yield 100 + } + + async uploadLandingPage(metadata: LandingPageMetadata) { + const linkPrefix = `${sample(BEE_HOSTS)!}/bzz` + const encodedMetadata = JSON.stringify({ + name: metadata.name, + size: metadata.size, + provider: 'swarm', + link: urlcat(linkPrefix, '/:txId', { txId: metadata.txId }), + signed: await makeFileKeySigned(metadata.key), + createdAt: new Date().toISOString(), + }) + const response = await fetch(landing) + const text = await response.text() + const replaced = text + .replace('Arweave', SwarmAgent.providerName) + .replace('Over Arweave', `Over ${SwarmAgent.providerName}`) + .replace('__METADATA__', encodedMetadata) + const data = encodeText(replaced) + return this.makePayload(data, 'text/html', 'landing.html') + } +} + +export default new SwarmAgent() diff --git a/packages/plugins/FileService/src/helpers.ts b/packages/plugins/FileService/src/helpers.ts index c64f928179cc..32ba38fdac51 100644 --- a/packages/plugins/FileService/src/helpers.ts +++ b/packages/plugins/FileService/src/helpers.ts @@ -1,9 +1,12 @@ import { type TypedMessage, createTypedMessageMetadataReader } from '@masknet/shared-base' import { META_KEY_1, META_KEY_2 } from './constants' -import type { FileInfo, FileInfoV1 } from './types' +import { FileInfo, FileInfoV1, Provider } from './types' import schemaV1 from './schema-v1.json' import schemaV2 from './schema-v2.json' import type { Result } from 'ts-results' +import { isNil } from 'lodash-unified' +import { encodeArrayBuffer, encodeText } from '@dimensiondev/kit' +import { createLookupTableResolver } from '@masknet/web3-kit' const reader_v1 = createTypedMessageMetadataReader(META_KEY_1, schemaV1) const reader_v2 = createTypedMessageMetadataReader(META_KEY_2, schemaV2) @@ -15,5 +18,25 @@ export function FileInfoMetadataReader(meta: TypedMessage['meta']): Result( + { + [Provider.arweave]: 'https://arweave.net', + [Provider.ipfs]: 'https://infura-ipfs.io/ipfs', + [Provider.swarm]: 'https://bee-2.gateway.ethswarm.org/bzz', + }, + () => 'Unknown provider', +) diff --git a/packages/plugins/FileService/src/locales/en-US.json b/packages/plugins/FileService/src/locales/en-US.json index 886a347e7444..dce87c305d8c 100644 --- a/packages/plugins/FileService/src/locales/en-US.json +++ b/packages/plugins/FileService/src/locales/en-US.json @@ -24,5 +24,8 @@ "drop_here": "Drop a file here to upload", "error_101": "The input is not a single file.", "error_102": "The file is too large; limit is {{limit}}.", - "on_change_file": "Change File" + "on_change_file": "Change File", + "provider_arweave": "Arweave", + "provider_ipfs": "IPFS", + "provider_swarm": "Swarm" } diff --git a/packages/plugins/FileService/src/schema-v2.json b/packages/plugins/FileService/src/schema-v2.json index 7cbeec0ef4e0..a445ecfaa153 100644 --- a/packages/plugins/FileService/src/schema-v2.json +++ b/packages/plugins/FileService/src/schema-v2.json @@ -9,7 +9,7 @@ }, "provider": { "type": "string", - "enum": ["arweave"], + "enum": ["arweave", "ipfs", "swarm"], "title": "provider" }, "id": { diff --git a/packages/plugins/FileService/src/types.ts b/packages/plugins/FileService/src/types.ts index d6974a67719d..7713dc7bba1c 100644 --- a/packages/plugins/FileService/src/types.ts +++ b/packages/plugins/FileService/src/types.ts @@ -1,6 +1,39 @@ +export enum Provider { + arweave = 'arweave', + ipfs = 'ipfs', + swarm = 'swarm', +} + +export interface ProviderConfig { + provider: Provider + name: string +} + +export interface LandingPageMetadata { + key: string | null | undefined + name: string + size: number + type: string + txId: string + useCDN: boolean +} + +export interface AttachmentOptions { + key?: string | null + type: string + block: Uint8Array + name: string +} + +export interface ProviderAgent { + makeAttachment(options: AttachmentOptions): Promise + upload(id: string): AsyncGenerator + uploadLandingPage(metadata: LandingPageMetadata): Promise +} + export interface FileInfo { type: 'file' - provider: 'arweave' + provider: Provider id: string name: string @@ -15,3 +48,5 @@ export interface FileInfo { export type FileInfoV1 = Omit & { type: 'arweave' } + +export type DialogCloseCallback = () => void diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e76a3ca94db8..173a4b3b333c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -681,32 +681,40 @@ importers: packages/plugins/FileService: specifiers: '@dimensiondev/common-protocols': 1.6.0-20210723072221-8f46a92 + '@ethersphere/bee-js': ^3.3.1 '@masknet/icons': workspace:* '@masknet/plugin-infra': workspace:* '@masknet/plugin-wallet': workspace:* '@masknet/shared': workspace:* '@masknet/shared-base': workspace:* '@masknet/theme': workspace:* + '@masknet/web3-kit': workspace:* '@msgpack/msgpack': ^2.7.2 arweave: ^1.10.3 date-fns: ^2.28.0 + ipfs-http-client: ^54.0.2 react-feather: ^2.0.9 react-router-dom: '5' react-use: ^17.3.2 + urlcat: ^2.0.4 dependencies: '@dimensiondev/common-protocols': 1.6.0-20210723072221-8f46a92 + '@ethersphere/bee-js': 3.3.1 '@masknet/icons': link:../../icons '@masknet/plugin-infra': link:../../plugin-infra '@masknet/plugin-wallet': link:../Wallet '@masknet/shared': link:../../shared '@masknet/shared-base': link:../../shared-base '@masknet/theme': link:../../theme + '@masknet/web3-kit': link:../../web3-kit '@msgpack/msgpack': 2.7.2 arweave: 1.10.13 date-fns: 2.28.0 + ipfs-http-client: 54.0.2 react-feather: 2.0.9_react@18.0.0-rc.0 react-router-dom: 5.3.0_react@18.0.0-rc.0 react-use: 17.3.2_757a802188413a36d4f24237d13b8e90 + urlcat: 2.0.4 packages/plugins/Flow: specifiers: @@ -4125,6 +4133,27 @@ packages: ethereumjs-util: 7.1.3 dev: false + /@ethersphere/bee-js/3.3.1: + resolution: {integrity: sha512-4XiyUD9sYZJ5Wac4/2oRKDfoUlQjea3xxMj80nqsDCUfrUTzIGwTdRbLSA7LhINbCZMP7vn9EVRq/DYe0kFGOA==} + engines: {bee: 1.4.3-1213e063, beeApiVersion: 2.0.0, beeDebugApiVersion: 1.2.1, node: '>=12.0.0', npm: '>=6.0.0'} + dependencies: + '@types/readable-stream': 2.3.13 + bufferutil: 4.0.6 + cross-blob: 2.0.1 + elliptic: 6.5.4 + isomorphic-ws: 4.0.1_ws@8.5.0 + js-sha3: 0.8.0 + ky: 0.25.1 + ky-universal: 0.8.2_9d16fb08bee5537b54f3df10881f880a + semver: 7.3.5 + tar-js: 0.3.0 + utf-8-validate: 5.0.8 + web-streams-polyfill: 3.2.0 + ws: 8.5.0_bffff4271b89d628e8333ead80d3d8e8 + transitivePeerDependencies: + - domexception + dev: false + /@ethersproject/abi/5.0.7: resolution: {integrity: sha512-Cqktk+hSIckwP/W8O47Eef60VwmoSC/L3lY0+dIBhQPCNn9E4V7rwmm2aFrNRRDJfFlGuZ1khkQUOc3oBX+niw==} dependencies: @@ -4620,6 +4649,19 @@ packages: browser-headers: 0.4.1 dev: false + /@ipld/dag-cbor/6.0.15: + resolution: {integrity: sha512-Vm3VTSTwlmGV92a3C5aeY+r2A18zbH2amehNhsX8PBa3muXICaWrN8Uri85A5hLH7D7ElhE8PdjxD6kNqUmTZA==} + dependencies: + cborg: 1.6.1 + multiformats: 9.6.4 + dev: false + + /@ipld/dag-pb/2.1.15: + resolution: {integrity: sha512-qkoUIiuQDx2ZN+YmYFdSNNHRt15p1XTYbqsseb8DgA0ACcqCUurbiNVd0jt5GuiBm76t2mOV2cZsNu6rykRFBQ==} + dependencies: + multiformats: 9.6.4 + dev: false + /@istanbuljs/load-nyc-config/1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -8664,6 +8706,13 @@ packages: '@types/scheduler': 0.16.2 csstype: 3.0.10 + /@types/readable-stream/2.3.13: + resolution: {integrity: sha512-4JSCx8EUzaW9Idevt+9lsRAt1lcSccoQfE+AouM1gk8sFxnnytKNIO3wTl9Dy+4m6jRJ1yXhboLHHT/LXBQiEw==} + dependencies: + '@types/node': 17.0.18 + safe-buffer: 5.2.1 + dev: false + /@types/remarkable/2.0.3: resolution: {integrity: sha512-QQUBeYApuHCNl9Br6ZoI3PlKmwZ69JHrlJktJXnjxobia9liZgsI70fm8PnCqVFAcefYK+9PGzR5L/hzCslNYQ==} dependencies: @@ -8843,7 +8892,7 @@ packages: dependencies: '@types/yargs-parser': 20.2.1 dev: true - + /@typescript-eslint/eslint-plugin/5.11.0_04e76d06685fd3c953373317ef352814: resolution: {integrity: sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -9737,6 +9786,17 @@ packages: resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} dev: true + /any-signal/2.1.2: + resolution: {integrity: sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ==} + dependencies: + abort-controller: 3.0.0 + native-abort-controller: 1.0.4_abort-controller@3.0.0 + dev: false + + /any-signal/3.0.0: + resolution: {integrity: sha512-l1H1GEkGGIXVGfCtvq8N68YI7gHajmfzRdKhmb8sGyAQpLCblirLa8eB09j4uKaiwe7vodAChocUf7AT3mYq5g==} + dev: false + /anymatch/2.0.0: resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} dependencies: @@ -11050,6 +11110,16 @@ packages: /blakejs/1.1.1: resolution: {integrity: sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==} + /blob-polyfill/5.0.20210201: + resolution: {integrity: sha512-SrH6IG6aXL9pCgSysBCiDpGcAJ1j6/c1qCwR3sTEQJhb+MTk6FITNA6eW6WNYQDNZVi4Z9GjxH5v2MMTv59CrQ==} + dev: false + + /blob-to-it/1.0.4: + resolution: {integrity: sha512-iCmk0W4NdbrWgRRuxOriU8aM5ijeVLI61Zulsmg/lUHNr7pYjoj+U77opLefNagevtrrbMt3JQ5Qip7ar178kA==} + dependencies: + browser-readablestream-to-it: 1.0.3 + dev: false + /blob/0.0.5: resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==} dev: false @@ -11212,6 +11282,10 @@ packages: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: false + /browser-readablestream-to-it/1.0.3: + resolution: {integrity: sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==} + dev: false + /browserify-aes/1.2.0: resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} dependencies: @@ -11376,7 +11450,6 @@ packages: dependencies: node-gyp-build: 4.3.0 dev: false - optional: true /builtin-modules/1.1.1: resolution: {integrity: sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=} @@ -11590,6 +11663,11 @@ packages: resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} dev: false + /cborg/1.6.1: + resolution: {integrity: sha512-dOGlTG610S6t3j7EYFxPBH7KiF1OlSAdWtMI4Iv1dabcId/L/nUvkfOEPge+vDp9YoPerEMiDoy5+Vm2oEqmQw==} + hasBin: true + dev: false + /ccount/1.1.0: resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} dev: true @@ -12446,6 +12524,16 @@ packages: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true + /cross-blob/2.0.1: + resolution: {integrity: sha512-ARuKPPo3I6DSqizal4UCyMCiGPQdMpMJS3Owx6Lleuh26vSt2UnfWRwbMLCYqbJUrcol+KzGVSLR91ezSHP80A==} + engines: {node: ^10.17.0 || >=12.3.0} + dependencies: + blob-polyfill: 5.0.20210201 + fetch-blob: 2.1.2 + transitivePeerDependencies: + - domexception + dev: false + /cross-fetch/3.0.6: resolution: {integrity: sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==} dependencies: @@ -12980,6 +13068,20 @@ packages: assert-plus: 1.0.0 dev: true + /data-uri-to-buffer/3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + dev: false + + /data-urls/2.0.0: + resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} + engines: {node: '>=10'} + dependencies: + abab: 2.0.5 + whatwg-mimetype: 2.3.0 + whatwg-url: 8.7.0 + dev: true + /data-urls/3.0.1: resolution: {integrity: sha512-Ds554NeT5Gennfoo9KN50Vh6tpgtvYEwraYjejXnyTpu1C7oXKxdFk75REooENHE8ndTVOJuv+BEs4/J/xcozw==} engines: {node: '>=12'} @@ -13338,6 +13440,17 @@ packages: resolution: {integrity: sha1-s55/HabrCnW6nBcySzR1PEfgZU0=} dev: true + /dns-over-http-resolver/1.2.3: + resolution: {integrity: sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA==} + dependencies: + debug: 4.3.3 + native-fetch: 3.0.0 + receptacle: 1.3.2 + transitivePeerDependencies: + - node-fetch + - supports-color + dev: false + /dns-packet/1.3.4: resolution: {integrity: sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==} dependencies: @@ -13534,6 +13647,13 @@ packages: resolution: {integrity: sha512-Hj+GY0mf2NNBcgg0v6DsV0/EYI5xDLUICZ5BSLVC+8uFEzoWpWw2VR6e3eWKgGjL2YEybUdg89UXDEHaBmi03Q==} dev: false + /electron-fetch/1.7.4: + resolution: {integrity: sha512-+fBLXEy4CJWQ5bz8dyaeSG1hD6JJ15kBZyj3eh24pIVrd3hLM47H/umffrdQfS6GZ0falF0g9JT9f3Rs6AVUhw==} + engines: {node: '>=6'} + dependencies: + encoding: 0.1.13 + dev: false + /electron-to-chromium/1.4.28: resolution: {integrity: sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg==} @@ -14838,6 +14958,10 @@ packages: resolution: {integrity: sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==} dev: true + /fast-fifo/1.1.0: + resolution: {integrity: sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==} + dev: false + /fast-glob/2.2.7: resolution: {integrity: sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==} engines: {node: '>=4.0.0'} @@ -14941,6 +15065,16 @@ packages: ua-parser-js: 0.7.31 dev: false + /fetch-blob/2.1.2: + resolution: {integrity: sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==} + engines: {node: ^10.17.0 || >=12.3.0} + peerDependencies: + domexception: '*' + peerDependenciesMeta: + domexception: + optional: true + dev: false + /fetch-ponyfill/4.1.0: resolution: {integrity: sha1-rjzl9zLGReq4fkroeTQUcJsjmJM=} dependencies: @@ -15513,6 +15647,10 @@ packages: has: 1.0.3 has-symbols: 1.0.2 + /get-iterator/1.0.2: + resolution: {integrity: sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==} + dev: false + /get-package-type/0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -16635,6 +16773,18 @@ packages: css-in-js-utils: 2.0.1 dev: false + /interface-datastore/6.1.0: + resolution: {integrity: sha512-oNHdsrWBsI/kDwUtEgt+aaZtQFKtQYN0TGZzc3SGiIA6m+plZ6malhmsygtbmDpfpIsNNC7ce9Gyaj+Tki+gVw==} + dependencies: + interface-store: 2.0.2 + nanoid: 3.2.0 + uint8arrays: 3.0.0 + dev: false + + /interface-store/2.0.2: + resolution: {integrity: sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==} + dev: false + /internal-slot/1.0.3: resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} engines: {node: '>= 0.4'} @@ -16661,6 +16811,11 @@ packages: resolution: {integrity: sha1-EEqOSqym09jNFXqO+L+rLXo//bY=} engines: {node: '>=0.10.0'} + /ip-regex/4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + dev: false + /ip/1.1.5: resolution: {integrity: sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=} dev: true @@ -16674,6 +16829,98 @@ packages: engines: {node: '>= 10'} dev: true + /ipfs-core-types/0.8.4: + resolution: {integrity: sha512-sbRZA1QX3xJ6ywTiVQZMOxhlhp4osAZX2SXx3azOLxAtxmGWDMkHYt722VV4nZ2GyJy8qyk5GHQIZ0uvQnpaTg==} + dependencies: + interface-datastore: 6.1.0 + multiaddr: 10.0.1 + multiformats: 9.6.4 + transitivePeerDependencies: + - node-fetch + - supports-color + dev: false + + /ipfs-core-utils/0.12.2: + resolution: {integrity: sha512-RfxP3rPhXuqKIUmTAUhmee6fmaV3A7LMnjOUikRKpSyqESz/DR7aGK7tbttMxkZdkSEr0rFXlqbyb0vVwmn0wQ==} + dependencies: + any-signal: 2.1.2 + blob-to-it: 1.0.4 + browser-readablestream-to-it: 1.0.3 + debug: 4.3.3 + err-code: 3.0.1 + ipfs-core-types: 0.8.4 + ipfs-unixfs: 6.0.6 + ipfs-utils: 9.0.4 + it-all: 1.0.6 + it-map: 1.0.6 + it-peekable: 1.0.3 + it-to-stream: 1.0.0 + merge-options: 3.0.4 + multiaddr: 10.0.1 + multiaddr-to-uri: 8.0.0 + multiformats: 9.6.4 + nanoid: 3.2.0 + parse-duration: 1.0.2 + timeout-abort-controller: 1.1.1 + uint8arrays: 3.0.0 + transitivePeerDependencies: + - node-fetch + - supports-color + dev: false + + /ipfs-http-client/54.0.2: + resolution: {integrity: sha512-gPTD+Hy7UdiM+Ez9n+PJmSQ5m1fShhDhIkZ+lTY2L9p7KFOU643qACqUrdZZjlZIRWHg4htHSYNPnTRh2bjOjA==} + engines: {node: '>=14.0.0', npm: '>=3.0.0'} + dependencies: + '@ipld/dag-cbor': 6.0.15 + '@ipld/dag-pb': 2.1.15 + abort-controller: 3.0.0 + any-signal: 2.1.2 + debug: 4.3.3 + err-code: 3.0.1 + ipfs-core-types: 0.8.4 + ipfs-core-utils: 0.12.2 + ipfs-utils: 9.0.4 + it-first: 1.0.7 + it-last: 1.0.6 + merge-options: 3.0.4 + multiaddr: 10.0.1 + multiformats: 9.6.4 + native-abort-controller: 1.0.4_abort-controller@3.0.0 + parse-duration: 1.0.2 + stream-to-it: 0.2.4 + uint8arrays: 3.0.0 + transitivePeerDependencies: + - node-fetch + - supports-color + dev: false + + /ipfs-unixfs/6.0.6: + resolution: {integrity: sha512-gTkjYKXuHnqIf6EFfS+ESaYEl3I3aaQQ0UX8MhpNzreMLEuMnuqpoI/uLLllTZa31WRplKixabbpRTSmTYRNwA==} + engines: {node: '>=14.0.0', npm: '>=6.0.0'} + dependencies: + err-code: 3.0.1 + protobufjs: 6.11.2 + dev: false + + /ipfs-utils/9.0.4: + resolution: {integrity: sha512-cfLKk004KLoEWJhBx4zg3mCro6mkiNhyGIlT7OZX9zxO1UqvLWpvW7cSZ1b1fbUIZ8qI7X2B7PeKlXC7jSfZ7g==} + dependencies: + any-signal: 3.0.0 + buffer: 6.0.3 + electron-fetch: 1.7.4 + err-code: 3.0.1 + is-electron: 2.2.1 + iso-url: 1.2.1 + it-glob: 1.0.2 + it-to-stream: 1.0.0 + merge-options: 3.0.4 + nanoid: 3.2.0 + native-fetch: 3.0.0_@achingbrain+node-fetch@2.6.7 + node-fetch: /@achingbrain/node-fetch/2.6.7 + react-native-fetch-api: 2.0.0 + stream-to-it: 0.2.4 + /ipld-dag-cbor/0.17.1: resolution: {integrity: sha512-Bakj/cnxQBdscORyf4LRHxQJQfoaY8KWc7PWROQgX+aw5FCzBt8ga0VM/59K+ABOznsqNvyLR/wz/oYImOpXJw==} engines: {node: '>=6.0.0', npm: '>=3.0.0'} @@ -16852,6 +17099,10 @@ packages: is-window: 1.0.2 dev: true + /is-electron/2.2.1: + resolution: {integrity: sha512-r8EEQQsqT+Gn0aXFx7lTFygYQhILLCB+wn0WCDL5LZRINeLH/Rvw1j2oKodELLXYNImQ3CRlVsY8wW4cGOsyuw==} + dev: false + /is-extendable/0.1.1: resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=} engines: {node: '>=0.10.0'} @@ -16930,6 +17181,13 @@ packages: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} dev: true + /is-ip/3.1.0: + resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} + engines: {node: '>=8'} + dependencies: + ip-regex: 4.3.0 + dev: false + /is-map/2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} dev: true @@ -17001,7 +17259,6 @@ packages: /is-plain-obj/2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} - dev: true /is-plain-obj/3.0.0: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} @@ -17161,9 +17418,9 @@ packages: /isexe/2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} - /iso-url/0.4.7: - resolution: {integrity: sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==} - engines: {node: '>=10'} + /iso-url/1.2.1: + resolution: {integrity: sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==} + engines: {node: '>=12'} dev: false /isobject/2.1.0: @@ -17216,6 +17473,14 @@ packages: ws: 7.5.5 dev: false + /isomorphic-ws/4.0.1_ws@8.5.0: + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' + dependencies: + ws: 8.5.0_bffff4271b89d628e8333ead80d3d8e8 + dev: false + /isstream/0.1.2: resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} dev: false @@ -17274,6 +17539,44 @@ packages: is-object: 1.0.2 dev: false + /it-all/1.0.6: + resolution: {integrity: sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==} + dev: false + + /it-first/1.0.7: + resolution: {integrity: sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==} + dev: false + + /it-glob/1.0.2: + resolution: {integrity: sha512-Ch2Dzhw4URfB9L/0ZHyY+uqOnKvBNeS/SMcRiPmJfpHiM0TsUZn+GkpcZxAoF3dJVdPm/PuIk3A4wlV7SUo23Q==} + dependencies: + '@types/minimatch': 3.0.5 + minimatch: 3.0.5 + dev: false + + /it-last/1.0.6: + resolution: {integrity: sha512-aFGeibeiX/lM4bX3JY0OkVCFkAw8+n9lkukkLNivbJRvNz8lI3YXv5xcqhFUV2lDJiraEK3OXRDbGuevnnR67Q==} + dev: false + + /it-map/1.0.6: + resolution: {integrity: sha512-XT4/RM6UHIFG9IobGlQPFQUrlEKkU4eBUFG3qhWhfAdh1JfF2x11ShCrKCdmZ0OiZppPfoLuzcfA4cey6q3UAQ==} + dev: false + + /it-peekable/1.0.3: + resolution: {integrity: sha512-5+8zemFS+wSfIkSZyf0Zh5kNN+iGyccN02914BY4w/Dj+uoFEoPSvj5vaWn8pNZJNSxzjW0zHRxC3LUb2KWJTQ==} + dev: false + + /it-to-stream/1.0.0: + resolution: {integrity: sha512-pLULMZMAB/+vbdvbZtebC0nWBTbG581lk6w8P7DfIIIKUfa8FbY7Oi0FxZcFPbxvISs7A9E+cMpLDBc1XhpAOA==} + dependencies: + buffer: 6.0.3 + fast-fifo: 1.1.0 + get-iterator: 1.0.2 + p-defer: 3.0.0 + p-fifo: 1.0.0 + readable-stream: 3.6.0 + dev: false + /iterate-iterator/1.0.2: resolution: {integrity: sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==} dev: true @@ -18241,6 +18544,29 @@ packages: engines: {node: '>= 8'} dev: true + /ky-universal/0.8.2_9d16fb08bee5537b54f3df10881f880a: + resolution: {integrity: sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==} + engines: {node: '>=10.17'} + peerDependencies: + ky: '>=0.17.0' + web-streams-polyfill: '>=2.0.0' + peerDependenciesMeta: + web-streams-polyfill: + optional: true + dependencies: + abort-controller: 3.0.0 + ky: 0.25.1 + node-fetch: 3.0.0-beta.9 + web-streams-polyfill: 3.2.0 + transitivePeerDependencies: + - domexception + dev: false + + /ky/0.25.1: + resolution: {integrity: sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==} + engines: {node: '>=10'} + dev: false + /last-run/1.1.1: resolution: {integrity: sha1-RblpQsF7HHnHchmCWbqUO+v4yls=} engines: {node: '>= 0.10'} @@ -18634,7 +18960,6 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 - dev: true /lru_map/0.4.1: resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==} @@ -18884,6 +19209,13 @@ packages: /merge-descriptors/1.0.1: resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=} + /merge-options/3.0.4: + resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} + engines: {node: '>=10'} + dependencies: + is-plain-obj: 2.1.0 + dev: false + /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -19166,6 +19498,29 @@ packages: /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /multiaddr-to-uri/8.0.0: + resolution: {integrity: sha512-dq4p/vsOOUdVEd1J1gl+R2GFrXJQH8yjLtz4hodqdVbieg39LvBOdMQRdQnfbg5LSM/q1BYNVf5CBbwZFFqBgA==} + dependencies: + multiaddr: 10.0.1 + transitivePeerDependencies: + - node-fetch + - supports-color + dev: false + + /multiaddr/10.0.1: + resolution: {integrity: sha512-G5upNcGzEGuTHkzxezPrrD6CaIHR9uo+7MwqhNVcXTs33IInon4y7nMiGxl2CY5hG7chvYQUQhz5V52/Qe3cbg==} + dependencies: + dns-over-http-resolver: 1.2.3 + err-code: 3.0.1 + is-ip: 3.1.0 + multiformats: 9.6.4 + uint8arrays: 3.0.0 + varint: 6.0.0 + transitivePeerDependencies: + - node-fetch + - supports-color + dev: false + /multibase/0.6.1: resolution: {integrity: sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==} deprecated: This module has been superseded by the multiformats module @@ -19217,13 +19572,6 @@ packages: varint: 5.0.2 dev: false - /multicodec/3.2.1: - resolution: {integrity: sha512-+expTPftro8VAW8kfvcuNNNBgb9gPeNYV9dn+z1kJRWF2vih+/S79f2RVeIwmrJBUJ6NT9IUPWnZDQvegEh5pw==} - deprecated: This module has been superseded by the multiformats module - dependencies: - uint8arrays: 3.0.0 - varint: 6.0.0 - dev: false /multiformats/9.6.2: resolution: {integrity: sha512-1dKng7RkBelbEZQQD2zvdzYKgUmtggpWl+GXQBYhnEGGkV6VIYfWgV3VSeyhcUFFEelI5q4D0etCJZ7fbuiamQ==} @@ -19344,6 +19692,28 @@ packages: snapdragon: 0.8.2 to-regex: 3.0.2 + /native-abort-controller/1.0.4_abort-controller@3.0.0: + resolution: {integrity: sha512-zp8yev7nxczDJMoP6pDxyD20IU0T22eX8VwN2ztDccKvSZhRaV33yP1BGwKSZfXuqWUzsXopVFjBdau9OOAwMQ==} + peerDependencies: + abort-controller: '*' + dependencies: + abort-controller: 3.0.0 + dev: false + + /native-fetch/3.0.0: + resolution: {integrity: sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==} + peerDependencies: + node-fetch: '*' + dev: false + + /native-fetch/3.0.0_@achingbrain+node-fetch@2.6.7: + resolution: {integrity: sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==} + peerDependencies: + node-fetch: '*' + dependencies: + node-fetch: /@achingbrain/node-fetch/2.6.7 + dev: false + /natural-compare/1.4.0: resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} dev: true @@ -19434,6 +19804,16 @@ packages: dependencies: whatwg-url: 5.0.0 + /node-fetch/3.0.0-beta.9: + resolution: {integrity: sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==} + engines: {node: ^10.17 || >=12.3} + dependencies: + data-uri-to-buffer: 3.0.1 + fetch-blob: 2.1.2 + transitivePeerDependencies: + - domexception + dev: false + /node-forge/1.2.1: resolution: {integrity: sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==} engines: {node: '>= 6.13.0'} @@ -19889,6 +20269,11 @@ packages: engines: {node: '>=4'} dev: true + /p-defer/3.0.0: + resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==} + engines: {node: '>=8'} + dev: false + /p-event/4.2.0: resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} engines: {node: '>=8'} @@ -19896,6 +20281,13 @@ packages: p-timeout: 3.2.0 dev: true + /p-fifo/1.0.0: + resolution: {integrity: sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A==} + dependencies: + fast-fifo: 1.1.0 + p-defer: 3.0.0 + dev: false + /p-filter/2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -20041,6 +20433,10 @@ packages: pbkdf2: 3.1.2 safe-buffer: 5.2.1 + /parse-duration/1.0.2: + resolution: {integrity: sha512-Dg27N6mfok+ow1a2rj/nRjtCfaKrHUZV2SJpEn/s8GaVUSlf4GGRCRP1c13Hj+wfPKVMrFDqLMLITkYKgKxyyg==} + dev: false + /parse-entities/2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} dependencies: @@ -21081,6 +21477,12 @@ packages: /react-is/17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + /react-native-fetch-api/2.0.0: + resolution: {integrity: sha512-GOA8tc1EVYLnHvma/TU9VTgLOyralO7eATRuCDchQveXW9Fr9vXygyq9iwqmM7YRZ8qRJfEt9xOS7OYMdJvRFw==} + dependencies: + p-defer: 3.0.0 + dev: false + /react-popper-tooltip/3.1.1_757a802188413a36d4f24237d13b8e90: resolution: {integrity: sha512-EnERAnnKRptQBJyaee5GJScWNUKQPDD2ywvzZyUjst/wj5U64C8/CnSYLNEmP2hG0IJ3ZhtDxE8oDN+KOyavXQ==} peerDependencies: @@ -21420,6 +21822,12 @@ packages: dependencies: picomatch: 2.3.0 + /receptacle/1.3.2: + resolution: {integrity: sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==} + dependencies: + ms: 2.1.3 + dev: false + /rechoir/0.6.2: resolution: {integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=} engines: {node: '>= 0.10'} @@ -21861,6 +22269,10 @@ packages: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} + /retimer/2.0.0: + resolution: {integrity: sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==} + dev: false + /retry/0.12.0: resolution: {integrity: sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=} engines: {node: '>= 4'} @@ -22238,7 +22650,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: true /send/0.17.2: resolution: {integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==} @@ -22850,6 +23261,12 @@ packages: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} dev: true + /stream-to-it/0.2.4: + resolution: {integrity: sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==} + dependencies: + get-iterator: 1.0.2 + dev: false + /strict-uri-encode/1.1.0: resolution: {integrity: sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=} engines: {node: '>=0.10.0'} @@ -23242,6 +23659,10 @@ packages: through: 2.3.8 dev: false + /tar-js/0.3.0: + resolution: {integrity: sha1-aUmqv7C6GLsVYq5RpDn9DzAYOhc=} + dev: false + /tar/4.4.15: resolution: {integrity: sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA==} engines: {node: '>=4.5'} @@ -23569,6 +23990,13 @@ packages: resolution: {integrity: sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=} engines: {node: '>=0.10.0'} + /timeout-abort-controller/1.1.1: + resolution: {integrity: sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ==} + dependencies: + abort-controller: 3.0.0 + retimer: 2.0.0 + dev: false + /timers-browserify/2.0.12: resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} engines: {node: '>=0.6.0'} @@ -24559,7 +24987,6 @@ packages: dependencies: node-gyp-build: 4.3.0 dev: false - optional: true /utf8/2.1.2: resolution: {integrity: sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=} @@ -24854,6 +25281,11 @@ packages: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} dev: true + /web-streams-polyfill/3.2.0: + resolution: {integrity: sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==} + engines: {node: '>= 8'} + dev: false + /web3-bzz/1.5.2: resolution: {integrity: sha512-W/sPCdA+XQ9duUYKHAwf/g69cbbV8gTCRsa1MpZwU7spXECiyJ2EvD/QzAZ+UpJk3GELXFF/fUByeZ3VRQKF2g==} engines: {node: '>=8.0.0'} @@ -25806,6 +26238,22 @@ packages: utf-8-validate: optional: true + /ws/8.5.0_bffff4271b89d628e8333ead80d3d8e8: + resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.6 + utf-8-validate: 5.0.8 + dev: false + /xdg-basedir/4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} @@ -25893,7 +26341,6 @@ packages: /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true /yaml/1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}