From 4a2c99478fdcb129da260c1fc14da0ba1842e5ff Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 16 May 2024 13:41:37 +0100 Subject: [PATCH] feat!: add download URL method to `BlobsStorage` (#1469) Ensures we don't hardcode the downlaod URL in the implementation. --- packages/upload-api/src/blob/accept.js | 16 ++++------- packages/upload-api/src/types/blob.ts | 2 ++ .../upload-api/test/handlers/web3.storage.js | 9 +++--- .../test/storage/blobs-storage-tests.js | 28 ++++++++++++++++++- .../upload-api/test/storage/blobs-storage.js | 17 ++++++++++- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/packages/upload-api/src/blob/accept.js b/packages/upload-api/src/blob/accept.js index 17f0fe173..456e143a3 100644 --- a/packages/upload-api/src/blob/accept.js +++ b/packages/upload-api/src/blob/accept.js @@ -8,9 +8,6 @@ import { code as rawCode } from 'multiformats/codecs/raw' import * as API from '../types.js' import { AllocatedMemoryHadNotBeenWrittenTo } from './lib.js' -const R2_REGION = 'auto' -const R2_BUCKET = 'carpark-prod-0' - /** * @param {API.W3ServiceContext} context * @returns {API.ServiceMethod} @@ -32,13 +29,10 @@ export function blobAcceptProvider(context) { const digest = Digest.decode(blob.digest) const content = createLink(rawCode, digest) - const url = - /** @type {API.URI<'https:'>} */ - ( - `https://w3s.link/ipfs/${content}?format=raw&origin=${encodeURIComponent( - `r2://${R2_REGION}/${R2_BUCKET}` - )}` - ) + const createUrl = await context.blobsStorage.createDownloadUrl(digest.bytes) + if (createUrl.error) { + return createUrl + } const locationClaim = await Assert.location.delegate({ issuer: context.id, @@ -46,7 +40,7 @@ export function blobAcceptProvider(context) { with: context.id.toDIDKey(), nb: { content, - location: [url], + location: [createUrl.ok], }, expiration: Infinity, }) diff --git a/packages/upload-api/src/types/blob.ts b/packages/upload-api/src/types/blob.ts index 7fbb855f2..ae3e34d3e 100644 --- a/packages/upload-api/src/types/blob.ts +++ b/packages/upload-api/src/types/blob.ts @@ -4,6 +4,7 @@ import type { Result, Failure, DID, + URI, } from '@ucanto/interface' import { Multihash, @@ -84,4 +85,5 @@ export interface BlobsStorage { Failure > > + createDownloadUrl: (content: Multihash) => Promise> } diff --git a/packages/upload-api/test/handlers/web3.storage.js b/packages/upload-api/test/handlers/web3.storage.js index 899577669..1078b7b3d 100644 --- a/packages/upload-api/test/handlers/web3.storage.js +++ b/packages/upload-api/test/handlers/web3.storage.js @@ -15,6 +15,7 @@ import { provisionProvider } from '../helpers/utils.js' import { createServer, connect } from '../../src/lib.js' import { alice, bob, createSpace, registerSpace } from '../util.js' import { parseBlobAddReceiptNext } from '../helpers/blob.js' +import * as Result from '../helpers/result.js' /** * @type {API.Tests} @@ -611,11 +612,9 @@ export const test = { // @ts-expect-error nb unknown const locations = delegation.capabilities[0].nb.location assert.equal(locations.length, 1) - assert.ok( - locations[0].includes( - `https://w3s.link/ipfs/${content}?format=raw&origin=` - ) - ) + + const loc = Result.unwrap(await context.blobsStorage.createDownloadUrl(digest)) + assert.ok(locations.includes(loc)) }, 'web3.storage/blob/accept fails to provide site delegation when blob was not stored': async (assert, context) => { diff --git a/packages/upload-api/test/storage/blobs-storage-tests.js b/packages/upload-api/test/storage/blobs-storage-tests.js index 5936b6d47..a7bd2d3fe 100644 --- a/packages/upload-api/test/storage/blobs-storage-tests.js +++ b/packages/upload-api/test/storage/blobs-storage-tests.js @@ -1,6 +1,7 @@ import * as API from '../../src/types.js' - import { sha256 } from 'multiformats/hashes/sha2' +import * as Result from '../helpers/result.js' +import { equals } from 'multiformats/bytes' /** * @type {API.Tests} @@ -43,4 +44,29 @@ export const test = { const hasBlob = await blobsStorage.has(blob.digest) assert.ok(hasBlob.ok) }, + + 'should create valid download URL for blobs that can be used to read': + async (assert, { blobsStorage }) => { + const data = new Uint8Array([11, 22, 34, 44, 55]) + const digest = await sha256.digest(data) + const expires = 60 * 60 * 24 // 1 day + + const upload = Result.unwrap( + await blobsStorage.createUploadUrl(digest.bytes, data.length, expires) + ) + + await fetch(upload.url, { + method: 'PUT', + mode: 'cors', + body: data, + headers: upload.headers, + }) + + const downloadUrl = Result.unwrap( + await blobsStorage.createDownloadUrl(digest.bytes) + ) + + const res = await fetch(downloadUrl) + assert.ok(equals(new Uint8Array(await res.arrayBuffer()), data)) + } } diff --git a/packages/upload-api/test/storage/blobs-storage.js b/packages/upload-api/test/storage/blobs-storage.js index 595a9d2a3..575a609a9 100644 --- a/packages/upload-api/test/storage/blobs-storage.js +++ b/packages/upload-api/test/storage/blobs-storage.js @@ -26,6 +26,7 @@ export class BlobsStorage { const content = new Map() if (http) { const server = http.createServer(async (request, response) => { + const { pathname } = new URL(request.url || '/', url) if (request.method === 'PUT') { const buffer = new Uint8Array( parseInt(request.headers['content-length'] || '0') @@ -41,10 +42,17 @@ export class BlobsStorage { if (checksum !== request.headers['x-amz-checksum-sha256']) { response.writeHead(400, `checksum mismatch`) } else { - const { pathname } = new URL(request.url || '/', url) content.set(pathname, buffer) response.writeHead(200) } + } else if (request.method === 'GET') { + const data = content.get(pathname) + if (data) { + response.writeHead(200) + response.write(data) + } else { + response.writeHead(404) + } } else { response.writeHead(405) } @@ -202,4 +210,11 @@ export class BlobsStorage { }, } } + + /** @param {Uint8Array} multihash */ + async createDownloadUrl (multihash) { + const digest = digestDecode(multihash) + const url = new URL(this.#bucketPath(digest), this.baseURL) + return ok(/** @type {Types.URI} */ (url.toString())) + } }