feat: support for bzz.link cids when downloading files (#350)
* feat: detect and extract bzz.link cids into hash when downloading files * test: add quite thorough testsuite
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
export const META_FILE_NAME = '.swarmgatewaymeta.json'
|
export const META_FILE_NAME = '.swarmgatewaymeta.json'
|
||||||
export const PREVIEW_FILE_NAME = '.swarmgatewaypreview.jpeg'
|
export const PREVIEW_FILE_NAME = '.swarmgatewaypreview.jpeg'
|
||||||
export const PREVIEW_DIMENSIONS = { maxWidth: 250, maxHeight: 175 }
|
export const PREVIEW_DIMENSIONS = { maxWidth: 250, maxHeight: 175 }
|
||||||
|
export const BZZ_LINK_DOMAIN = process.env.REACT_APP_BZZ_LINK_DOMAIN || 'bzz.link'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { History } from '../../components/History'
|
|||||||
import { Context, defaultUploadOrigin } from '../../providers/File'
|
import { Context, defaultUploadOrigin } from '../../providers/File'
|
||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { extractSwarmHash } from '../../utils'
|
import { recognizeSwarmHash } from '../../utils'
|
||||||
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
|
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
|
||||||
import { FileNavigation } from './FileNavigation'
|
import { FileNavigation } from './FileNavigation'
|
||||||
|
|
||||||
@@ -71,20 +71,6 @@ export function Download(): ReactElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recognizeSwarmHash(value: string) {
|
|
||||||
if (value.length < 64) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash = extractSwarmHash(value)
|
|
||||||
|
|
||||||
if (hash) {
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FileNavigation active="DOWNLOAD" />
|
<FileNavigation active="DOWNLOAD" />
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { extractSwarmHash, extractSwarmCid, recognizeSwarmHash } from './index'
|
||||||
|
|
||||||
|
interface TestObject {
|
||||||
|
input: string
|
||||||
|
expectedOutput: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const correctHashes: TestObject[] = [
|
||||||
|
// non-encrypted
|
||||||
|
{
|
||||||
|
input: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||||
|
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||||
|
expectedOutput: 'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
// encrypted
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
expectedOutput:
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
expectedOutput:
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
expectedOutput:
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||||
|
expectedOutput:
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f/',
|
||||||
|
expectedOutput:
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fb7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3f',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const wrongHashes: string[] = [
|
||||||
|
// one character too long
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa',
|
||||||
|
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa',
|
||||||
|
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa',
|
||||||
|
'http://gateway.org/bzz/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa/',
|
||||||
|
'https://gateway.org/b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d3fa/',
|
||||||
|
|
||||||
|
// a bit shorter
|
||||||
|
'b7e53783114e4555384d7fd7154eb8c2e3f7c749c176dcb8f4015b08161b3d',
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('extractSwarmHash', () => {
|
||||||
|
test('should correctly extract hash', () => {
|
||||||
|
correctHashes.forEach(({ input, expectedOutput }) => {
|
||||||
|
const hash = extractSwarmHash(input)
|
||||||
|
expect(hash).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test('should not extract hash from incorrect inputs', () => {
|
||||||
|
wrongHashes.forEach(input => {
|
||||||
|
const hash = extractSwarmHash(input)
|
||||||
|
expect(hash).toBe(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const correctCids: TestObject[] = [
|
||||||
|
{
|
||||||
|
input: 'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.bzz.link',
|
||||||
|
expectedOutput: 'e80a3df165abbf275ae5480e9e51241d2e6368c4ed379771424af29ca35b29d4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'https://bah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vookq.bzz.link',
|
||||||
|
expectedOutput: 'd2f269c0b99d5bbbcdb93d7f0a85815ad23f851dd2fa94509124c401f7b57395',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const wrongCids: string[] = [
|
||||||
|
'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.another.domain',
|
||||||
|
'http://bah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vookq.bzz.link',
|
||||||
|
'https://not_cid.bzz.link',
|
||||||
|
'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.subdomain.bzz.link',
|
||||||
|
'https://bah5acgza5afd34lfvo7sowxfjahj4ujeduxgg2ge5u3zo4kcjlzjzi23fhka.subdomain.bzz.link',
|
||||||
|
'https://bah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vook.bzz.link',
|
||||||
|
'https://aah5acgza2lzgtqfztvn3xtnzhv7qvbmblljd7bi52l5jiueretcad55vookq.bzz.link',
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('extractSwarmCid', () => {
|
||||||
|
test('should correctly extract hash', () => {
|
||||||
|
correctCids.forEach(({ input, expectedOutput }) => {
|
||||||
|
const hash = extractSwarmCid(input)
|
||||||
|
expect(hash).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test('should not extract cid from incorrect urls', () => {
|
||||||
|
wrongCids.forEach(url => {
|
||||||
|
const hash = extractSwarmCid(url)
|
||||||
|
expect(hash).toBe(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('recognizeSwarmHash', () => {
|
||||||
|
test('should correctly extract hash', () => {
|
||||||
|
;[...correctHashes, ...correctCids].forEach(({ input, expectedOutput }) => {
|
||||||
|
const hash = recognizeSwarmHash(input)
|
||||||
|
expect(hash).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test('should not extract hash from incorrect inputs but instead return them', () => {
|
||||||
|
;[...wrongHashes, ...wrongCids].forEach(url => {
|
||||||
|
const hash = recognizeSwarmHash(url)
|
||||||
|
expect(hash).toBe(url)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
+36
-3
@@ -1,5 +1,7 @@
|
|||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import { Token } from '../models/Token'
|
import { Token } from '../models/Token'
|
||||||
|
import { decodeCid } from '@ethersphere/swarm-cid'
|
||||||
|
import { BZZ_LINK_DOMAIN } from '../constants'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if value is an integer
|
* Test if value is an integer
|
||||||
@@ -108,10 +110,41 @@ export function makeRetriablePromise<T>(fn: () => Promise<T>, maxRetries = 3, de
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractSwarmHash(string: string): string | null {
|
// Matches exactly 64 or 128 caracters alphanumeric characters that are surrounded by non-alpha num characters
|
||||||
const matches = string.match(/[a-fA-F0-9]{64,128}/)
|
const regexpMatchHash = /(?:^|[^a-f0-9]+)([a-f0-9]{64}|[a-f0-9]{128})(?:$|[^a-f0-9]+)/i
|
||||||
|
|
||||||
return (matches && matches[0]) || null
|
export function extractSwarmHash(string: string): string | undefined {
|
||||||
|
const matches = string.match(regexpMatchHash)
|
||||||
|
|
||||||
|
return (matches && matches[1]) || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches the CID from bzz-link subdomain
|
||||||
|
const regexpMatchCID = new RegExp(`https://(bah5acgza[a-z0-9]{52})\\.${BZZ_LINK_DOMAIN}`, 'i')
|
||||||
|
|
||||||
|
export function extractSwarmCid(s: string): string | undefined {
|
||||||
|
const matches = s.match(regexpMatchCID)
|
||||||
|
|
||||||
|
if (!matches || !matches[1]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cid = matches[1]
|
||||||
|
try {
|
||||||
|
const decodeResult = decodeCid(cid)
|
||||||
|
|
||||||
|
if (!decodeResult.type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeResult.reference
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recognizeSwarmHash(value: string): string {
|
||||||
|
return extractSwarmHash(value) || extractSwarmCid(value) || value
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uuidV4(): string {
|
export function uuidV4(): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user