Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions packages/core/storage-js/src/packages/StorageFileApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,22 +248,40 @@ export default class StorageFileApi extends BaseApiClient<StorageError> {
return this.handleOperation(async () => {
let body
const options = { upsert: DEFAULT_FILE_OPTIONS.upsert, ...fileOptions }
const headers: Record<string, string> = {
let headers: Record<string, string> = {
...this.headers,
...{ 'x-upsert': String(options.upsert as boolean) },
}

const metadata = options.metadata

if (typeof Blob !== 'undefined' && fileBody instanceof Blob) {
body = new FormData()
body.append('cacheControl', options.cacheControl as string)
if (metadata) {
body.append('metadata', this.encodeMetadata(metadata))
}
body.append('', fileBody)
} else if (typeof FormData !== 'undefined' && fileBody instanceof FormData) {
body = fileBody
body.append('cacheControl', options.cacheControl as string)
if (!body.has('cacheControl')) {
body.append('cacheControl', options.cacheControl as string)
}
if (metadata && !body.has('metadata')) {
body.append('metadata', this.encodeMetadata(metadata))
}
} else {
body = fileBody
headers['cache-control'] = `max-age=${options.cacheControl}`
headers['content-type'] = options.contentType as string

if (metadata) {
headers['x-metadata'] = this.toBase64(this.encodeMetadata(metadata))
}
}

if (fileOptions?.headers) {
headers = { ...headers, ...fileOptions.headers }
}

const data = await put(this.fetch, url.toString(), body as object, { headers })
Expand Down
42 changes: 42 additions & 0 deletions packages/core/storage-js/test/storageFileApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,48 @@ describe('StorageFileApi Edge Cases', () => {
expect(body.get('metadata')).toBe(JSON.stringify(metadata))
})

test('uploadToSignedUrl with Blob and metadata', async () => {
const testBlob = new Blob(['test content'], { type: 'text/plain' })
const metadata = { custom_id: '12345', author: 'test' }

await storage
.from('test-bucket')
.uploadToSignedUrl('test-path', 'test-token', testBlob, { metadata })

expect(mockPut).toHaveBeenCalled()
const [, , body] = mockPut.mock.calls[0] as [null, null, FormData]
expect(body.constructor.name).toBe('FormData')
expect(body.get('metadata')).toBe(JSON.stringify(metadata))
})

test('uploadToSignedUrl with FormData and metadata', async () => {
const testFormData = new FormData()
testFormData.append('file', 'test content')
const metadata = { custom_id: '12345' }

await storage
.from('test-bucket')
.uploadToSignedUrl('test-path', 'test-token', testFormData, { metadata })

expect(mockPut).toHaveBeenCalled()
const [, , body] = mockPut.mock.calls[0] as [null, null, FormData]
expect(body).toBe(testFormData)
expect(body.get('metadata')).toBe(JSON.stringify(metadata))
})

test('uploadToSignedUrl with binary body and metadata sets x-metadata header', async () => {
const metadata = { custom_id: '12345' }

await storage
.from('test-bucket')
.uploadToSignedUrl('test-path', 'test-token', 'raw-binary-content', { metadata })

expect(mockPut).toHaveBeenCalled()
const [, , , { headers }] = mockPut.mock.calls[0]
const expectedBase64 = Buffer.from(JSON.stringify(metadata)).toString('base64')
expect(headers['x-metadata']).toBe(expectedBase64)
})

test('upload passes headers', async () => {
const testFormData = new FormData()
testFormData.append('file', 'test content')
Expand Down