mirror of
https://github.com/alexgo-io/stacks.js.git
synced 2026-05-12 19:49:15 +08:00
Merge pull request #630 from blockstack/feature/listfiles-gaia-reconnection
Fix `listFiles` not performing gaia reconnection
This commit is contained in:
@@ -577,80 +577,86 @@ export function getAppBucketUrl(gaiaHubUrl: string, appPrivateKey: string) {
|
||||
* @returns {Promise} that resolves to the number of files listed.
|
||||
* @private
|
||||
*/
|
||||
function listFilesLoop(hubConfig: GaiaHubConfig,
|
||||
page: string | null,
|
||||
callCount: number,
|
||||
fileCount: number,
|
||||
callback: (name: string) => boolean): Promise<number> {
|
||||
async function listFilesLoop(
|
||||
caller: UserSession,
|
||||
hubConfig: GaiaHubConfig | null,
|
||||
page: string | null,
|
||||
callCount: number,
|
||||
fileCount: number,
|
||||
callback: (name: string) => boolean
|
||||
): Promise<number> {
|
||||
if (callCount > 65536) {
|
||||
// this is ridiculously huge, and probably indicates
|
||||
// a faulty Gaia hub anyway (e.g. on that serves endless data)
|
||||
throw new Error('Too many entries to list')
|
||||
}
|
||||
|
||||
let httpStatus
|
||||
const pageRequest = JSON.stringify({ page })
|
||||
|
||||
const fetchOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': `${pageRequest.length}`,
|
||||
Authorization: `bearer ${hubConfig.token}`
|
||||
},
|
||||
body: pageRequest
|
||||
hubConfig = hubConfig || await caller.getOrSetLocalGaiaHubConnection()
|
||||
let response: Response
|
||||
try {
|
||||
const pageRequest = JSON.stringify({ page })
|
||||
const fetchOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': `${pageRequest.length}`,
|
||||
Authorization: `bearer ${hubConfig.token}`
|
||||
},
|
||||
body: pageRequest
|
||||
}
|
||||
response = await fetch(`${hubConfig.server}/list-files/${hubConfig.address}`, fetchOptions)
|
||||
if (!response.ok) {
|
||||
throw new Error(`listFiles failed with HTTP status ${response.status}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// If error occurs on the first call, perform a gaia re-connection and retry.
|
||||
// Same logic as other gaia requests (putFile, getFile, etc).
|
||||
if (callCount === 0) {
|
||||
const freshHubConfig = await caller.setLocalGaiaHubConnection()
|
||||
return listFilesLoop(caller, freshHubConfig, page, callCount + 1, 0, callback)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
return fetch(`${hubConfig.server}/list-files/${hubConfig.address}`, fetchOptions)
|
||||
.then((response) => {
|
||||
httpStatus = response.status
|
||||
if (httpStatus >= 400) {
|
||||
throw new Error(`listFiles failed with HTTP status ${httpStatus}`)
|
||||
}
|
||||
return response.text()
|
||||
})
|
||||
.then(responseText => JSON.parse(responseText))
|
||||
.then((responseJSON) => {
|
||||
const entries = responseJSON.entries
|
||||
const nextPage = responseJSON.page
|
||||
if (entries === null || entries === undefined) {
|
||||
// indicates a misbehaving Gaia hub or a misbehaving driver
|
||||
// (i.e. the data is malformed)
|
||||
throw new Error('Bad listFiles response: no entries')
|
||||
}
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const rc = callback(entries[i])
|
||||
if (!rc) {
|
||||
// callback indicates that we're done
|
||||
return Promise.resolve(fileCount + i)
|
||||
}
|
||||
}
|
||||
if (nextPage && entries.length > 0) {
|
||||
// keep going -- have more entries
|
||||
return listFilesLoop(
|
||||
hubConfig, nextPage, callCount + 1, fileCount + entries.length, callback
|
||||
)
|
||||
} else {
|
||||
// no more entries -- end of data
|
||||
return Promise.resolve(fileCount + entries.length)
|
||||
}
|
||||
})
|
||||
const responseText = await response.text()
|
||||
const responseJSON = JSON.parse(responseText)
|
||||
const entries = responseJSON.entries
|
||||
const nextPage = responseJSON.page
|
||||
if (entries === null || entries === undefined) {
|
||||
// indicates a misbehaving Gaia hub or a misbehaving driver
|
||||
// (i.e. the data is malformed)
|
||||
throw new Error('Bad listFiles response: no entries')
|
||||
}
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const rc = callback(entries[i])
|
||||
if (!rc) {
|
||||
// callback indicates that we're done
|
||||
return fileCount + i
|
||||
}
|
||||
}
|
||||
if (nextPage && entries.length > 0) {
|
||||
// keep going -- have more entries
|
||||
return listFilesLoop(
|
||||
caller, hubConfig, nextPage, callCount + 1, fileCount + entries.length, callback
|
||||
)
|
||||
} else {
|
||||
// no more entries -- end of data
|
||||
return fileCount + entries.length
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List the set of files in this application's Gaia storage bucket.
|
||||
* @param {UserSession} caller - instance calling this method
|
||||
* @param {function} callback - a callback to invoke on each named file that
|
||||
* returns `true` to continue the listing operation or `false` to end it
|
||||
* @return {Promise} that resolves to the number of files listed
|
||||
*/
|
||||
export async function listFiles(
|
||||
export function listFiles(
|
||||
callback: (name: string) => boolean,
|
||||
caller?: UserSession
|
||||
): Promise<number> {
|
||||
const userSession = caller || new UserSession()
|
||||
const gaiaHubConfig = await userSession.getOrSetLocalGaiaHubConnection()
|
||||
return listFilesLoop(gaiaHubConfig, null, 0, 0, callback)
|
||||
caller = caller || new UserSession()
|
||||
return listFilesLoop(caller, null, null, 0, 0, callback)
|
||||
}
|
||||
|
||||
export { connectToGaiaHub, uploadToGaiaHub, BLOCKSTACK_GAIA_HUB_LABEL }
|
||||
|
||||
@@ -1032,4 +1032,68 @@ export function runStorageTests() {
|
||||
t.equal(count, 1, 'Count matches number of files')
|
||||
})
|
||||
})
|
||||
|
||||
test('listFiles gets a new gaia config and tries again', (t) => {
|
||||
t.plan(4)
|
||||
|
||||
const path = 'file.json'
|
||||
const listFilesUrl = 'https://hub.testblockstack.org/list-files/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yabc'
|
||||
const invalidHubConfig = {
|
||||
address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yabc',
|
||||
server: 'https://hub.testblockstack.org',
|
||||
token: '',
|
||||
url_prefix: 'https://gaia.testblockstack.org/hub/'
|
||||
}
|
||||
const validHubConfig = Object.assign({}, invalidHubConfig, {
|
||||
token: 'valid'
|
||||
})
|
||||
const connectToGaiaHub = sinon.stub().resolves(validHubConfig)
|
||||
|
||||
const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229'
|
||||
const UserSessionClass = proxyquire('../../../src/auth/userSession', {
|
||||
'../storage/hub': {
|
||||
connectToGaiaHub
|
||||
}
|
||||
}).UserSession as typeof UserSession
|
||||
|
||||
const appConfig = new AppConfig(['store_write'], 'http://localhost:3000')
|
||||
const blockstack = new UserSessionClass({ appConfig })
|
||||
blockstack.store.getSessionData().userData = <any>{
|
||||
appPrivateKey: privateKey,
|
||||
gaiaHubConfig: invalidHubConfig
|
||||
}
|
||||
|
||||
const { listFiles } = proxyquire('../../../src/storage', {
|
||||
'./hub': {
|
||||
connectToGaiaHub
|
||||
}
|
||||
})
|
||||
|
||||
let callCount = 0
|
||||
FetchMock.post(listFilesUrl, (url, { headers }) => {
|
||||
if ((<any>headers).Authorization === 'bearer ') {
|
||||
t.ok(true, 'tries with invalid token')
|
||||
return 401
|
||||
}
|
||||
callCount += 1
|
||||
if (callCount === 1) {
|
||||
return { entries: [path], page: callCount }
|
||||
} else if (callCount === 2) {
|
||||
return { entries: [], page: callCount }
|
||||
} else {
|
||||
throw new Error('Called too many times')
|
||||
}
|
||||
})
|
||||
|
||||
const files = []
|
||||
listFiles((name) => {
|
||||
files.push(name)
|
||||
return true
|
||||
}, blockstack)
|
||||
.then((count) => {
|
||||
t.equal(files.length, 1, 'Got one file back')
|
||||
t.equal(files[0], 'file.json', 'Got the right file back')
|
||||
t.equal(count, 1, 'Count matches number of files')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user