Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: renterd-js and core request api #592

Merged
merged 1 commit into from Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shy-fans-know.md
@@ -0,0 +1,5 @@
---
'@siafoundation/renterd-js': patch
---

Fixed an issue with upload and download content type and encoding. Closes https://github.com/SiaFoundation/web/issues/591
5 changes: 0 additions & 5 deletions libs/renterd-js/README.md
Expand Up @@ -84,11 +84,6 @@ export async function example() {
key: 'path/to/file.txt',
bucket: 'my-bucket',
},
config: {
onDownloadProgress: (progress) => {
console.log(progress.loaded / progress.total)
},
},
})
}
```
1 change: 1 addition & 0 deletions libs/renterd-js/src/autopilot.ts
Expand Up @@ -30,6 +30,7 @@ export function Autopilot({
}) {
const axios = initAxios(api, password)
return {
axios,
state: buildRequestHandler<
AutopilotStateParams,
AutopilotStatePayload,
Expand Down
1 change: 1 addition & 0 deletions libs/renterd-js/src/bus.ts
Expand Up @@ -268,6 +268,7 @@ import { buildRequestHandler, initAxios } from '@siafoundation/request'
export function Bus({ api, password }: { api: string; password?: string }) {
const axios = initAxios(api, password)
return {
axios,
busState: buildRequestHandler<
BusStateParams,
BusStatePayload,
Expand Down
5 changes: 0 additions & 5 deletions libs/renterd-js/src/example.ts
Expand Up @@ -73,10 +73,5 @@ export async function example() {
key: 'path/to/file.txt',
bucket: 'my-bucket',
},
config: {
onDownloadProgress: (progress) => {
console.log(progress.loaded / progress.total)
},
},
})
}
80 changes: 80 additions & 0 deletions libs/renterd-js/src/index.spec.ts
@@ -0,0 +1,80 @@
import MockAdapter from 'axios-mock-adapter'
import { Bus } from './bus'
import { Worker } from './worker'

describe('renterd-js', () => {
const api = 'https://sia.tech/api'
const password = 'password1337'

it('default data and headers support json', async () => {
const bus = Bus({
api,
password,
})
const mock = new MockAdapter(bus.axios)

const bucket = 'newbucket'
const url = `/bus/buckets`
const fullUrl = `${api}${url}`

mock.onPost(fullUrl).reply((config) => {
expect(config.url).toBe(url)
expect(config.method).toBe('post')
expect(config.headers?.['Content-Type']).toMatch(/application\/json/)
expect(config.data).toEqual(
JSON.stringify({
name: bucket,
})
)
return [200, { name: bucket }]
})

const response = await bus.bucketCreate({
data: {
name: bucket,
},
})

expect(response.status).toBe(200)
})

it('object upload should send file as multipart form data', async () => {
const worker = Worker({
api,
password,
})
const mock = new MockAdapter(worker.axios)

const bucket = 'default'
const name = 'test.txt'
const fileKey = `directory/${name}`
const url = `/worker/objects/${fileKey}?bucket=${bucket}`
const fullUrl = `${api}${url}`

mock.onPut(fullUrl).reply((config) => {
expect(config.url).toBe(url)
expect(config.method).toBe('put')
expect(config.headers?.['Content-Type']).toMatch(/multipart\/form-data/)
expect(config.data).toBeInstanceOf(File)
if (config.onUploadProgress) {
config.onUploadProgress({ loaded: 500, total: 1000 })
}
return [200, undefined]
})

const response = await worker.objectUpload({
params: {
key: fileKey,
bucket: bucket,
},
data: new File(['hello world'], name),
config: {
onUploadProgress: (progress) => {
expect(progress.loaded / progress.total).toBe(0.5)
},
},
})

expect(response.status).toBe(200)
})
})
23 changes: 20 additions & 3 deletions libs/renterd-js/src/worker.ts
Expand Up @@ -24,6 +24,7 @@ import { buildRequestHandler, initAxios } from '@siafoundation/request'
export function Worker({ api, password }: { api: string; password?: string }) {
const axios = initAxios(api, password)
return {
axios,
workerState: buildRequestHandler<
WorkerStateParams,
WorkerStatePayload,
Expand All @@ -33,17 +34,33 @@ export function Worker({ api, password }: { api: string; password?: string }) {
ObjectDownloadParams,
ObjectDownloadPayload,
ObjectDownloadResponse
>(axios, 'get', workerObjectsKeyRoute),
>(axios, 'get', workerObjectsKeyRoute, {
config: {
responseType: 'blob',
},
}),
objectUpload: buildRequestHandler<
ObjectUploadParams,
ObjectUploadPayload,
ObjectUploadResponse
>(axios, 'put', workerObjectsKeyRoute),
>(axios, 'put', workerObjectsKeyRoute, {
config: {
headers: {
'Content-Type': 'multipart/form-data',
},
},
}),
multipartUploadPart: buildRequestHandler<
MultipartUploadPartParams,
MultipartUploadPartPayload,
MultipartUploadPartResponse
>(axios, 'put', workerMultipartKeyRoute),
>(axios, 'put', workerMultipartKeyRoute, {
config: {
headers: {
'Content-Type': 'multipart/form-data',
},
},
}),
rhpScan: buildRequestHandler<
RhpScanParams,
RhpScanPayload,
Expand Down
3 changes: 2 additions & 1 deletion libs/request/package.json
Expand Up @@ -4,7 +4,8 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"axios": "^0.27.2"
"axios": "^0.27.2",
"@technically/lodash": "^4.17.0"
},
"types": "./src/index.d.ts"
}
54 changes: 31 additions & 23 deletions libs/request/src/index.ts
@@ -1,6 +1,7 @@
import {
Axios,
import { merge } from '@technically/lodash'
import axios, {
AxiosError,
AxiosInstance,
AxiosRequestConfig,
AxiosRequestHeaders,
AxiosResponse,
Expand Down Expand Up @@ -39,7 +40,15 @@ export function buildRequestHandler<
Params = void,
Data = void,
Response = void
>(axios: Axios, method: Method, route: string, defaultParams?: Params) {
>(
axios: AxiosInstance,
method: Method,
route: string,
options: {
defaultParams?: Params
config?: AxiosRequestConfig<Data>
} = {}
) {
type Args = Params extends void
? Data extends void
? { config?: AxiosRequestConfig<Data> } | void
Expand All @@ -55,26 +64,32 @@ export function buildRequestHandler<
config?: AxiosRequestConfig<Data>
}

type MaybeArgs = {
params?: Params
data?: Data
config?: AxiosRequestConfig<Data>
}

return (args: Args) => {
// args is sometimes undefined
const a = {
// Force remove the void type
const nonVoidArgs: MaybeArgs = {
...args,
} as {
params?: Params
data?: Data
config?: AxiosRequestConfig<Data>
}
const mergedArgs: MaybeArgs = {
...nonVoidArgs,
config: merge(options.config, nonVoidArgs.config),
}
const params = {
...defaultParams,
...a.params,
...options.defaultParams,
...mergedArgs.params,
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const paramRoute = parameterizeRoute(route, params as RequestParams)!

const data = 'data' in a ? JSON.stringify(a.data) : undefined
const data = 'data' in mergedArgs ? mergedArgs.data : undefined

return axios[method]<Response>(paramRoute, data, a?.config)
return axios[method]<Response>(paramRoute, data, mergedArgs?.config)
}
}

Expand All @@ -89,28 +104,21 @@ export async function to<T>(
> {
try {
const response = await promise
// Just in case the response is not already JSON
try {
const data = JSON.parse(response.data as string)
return [data, undefined, response]
} catch (e) {
return [response.data, undefined, response]
}
return [response.data, undefined, response]
} catch (error) {
return [undefined, error as AxiosError<T>, undefined]
}
}

export function initAxios(api: string, password?: string): Axios {
export function initAxios(api: string, password?: string): AxiosInstance {
const headers: AxiosRequestHeaders = {
'Content-Type': 'application/json',
}
if (password) {
headers['Authorization'] = `Basic ${btoa(`:${password}`)}`
}
return new Axios({
return axios.create({
baseURL: api,
headers,
responseType: 'json',
})
}
18 changes: 11 additions & 7 deletions libs/sia-central-js/src/index.ts
Expand Up @@ -72,7 +72,9 @@ export function SiaCentral({ api }: { api: string } = { api: defaultApi }) {
SiaCentralExchangeRatesPayload,
SiaCentralExchangeRatesResponse
>(axios, 'get', '/market/exchange-rate', {
currencies: 'sc',
defaultParams: {
currencies: 'sc',
},
}),
host: buildRequestHandler<
SiaCentralHostParams,
Expand All @@ -84,12 +86,14 @@ export function SiaCentral({ api }: { api: string } = { api: defaultApi }) {
SiaCentralHostsPayload,
SiaCentralHostsResponse
>(axios, 'get', '/hosts/list', {
showinactive: false,
sort: 'used_storage',
dir: 'desc',
protocol: 'rhp3',
limit: 10,
page: 1,
defaultParams: {
showinactive: false,
sort: 'used_storage',
dir: 'desc',
protocol: 'rhp3',
limit: 10,
page: 1,
},
}),
hostsNetworkAverages: buildRequestHandler<
SiaCentralHostsNetworkAveragesParams,
Expand Down