nfp_sites/filadelfia_archive/app/api.js

285 lines
7.1 KiB
JavaScript

const Authentication = require('./authentication')
const lang = require('./lang')
function safeParseReponse(str, status, url) {
if (status === 0) {
return new Error(lang.api_down)
}
if (str.slice(0, 9) === '<!doctype') {
if (status === 500) {
return new Error('Server is temporarily down, try again later.')
}
return new Error('Expected JSON but got HTML (' + status + ': ' + url.split('?')[0] + ')')
}
if (!str) {
return {}
}
try {
return JSON.parse(str)
} catch (err) {
return new Error('Unexpected non-JSON response: ' + err.message)
}
}
let requests = new Set()
let requestIndex = 0
function clearLoading(request) {
requests.delete(request)
if (!requests.size) {
api.loading = false
window.requestAnimationFrame(m.redraw.bind(m))
}
}
const api = {
loading: false,
sendRequest: function(options, isPagination) {
let request = requestIndex++
requests.add(request)
api.loading = true
let token = Authentication.getToken()
let pagination = isPagination
if (token) {
options.headers = options.headers || {}
options.headers['Authorization'] = 'Bearer ' + token
}
options.extract = function(xhr) {
let out = safeParseReponse(xhr.responseText, xhr.status, this.url)
if (out instanceof Error) {
throw out
}
if (xhr.status >= 300) {
if (out.message) {
throw out
}
console.error('Got error ' + xhr.status + ' but no error message:', out)
throw new Error('Unknown or empty response from server.')
}
if (pagination) {
let headers = {}
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) {
let splitted = item.split(': ')
headers[splitted[0]] = splitted[1]
})
out = {
headers: headers || {},
data: out,
}
}
return out
}
return m.request(options)
.then(function(res) {
clearLoading(request)
return res
})
.catch(function (error) {
clearLoading(request)
window.requestAnimationFrame(m.redraw.bind(m))
if (error.status === 403) {
Authentication.clearToken()
m.route.set('/', { redirect: m.route.get() })
}
if (error.response && error.response.status) {
return Promise.reject(error.response)
}
return Promise.reject(error)
})
},
uploadBanner: function(bannerFile, token, reporter) {
let request = requestIndex++
requests.add(request)
api.loading = true
var report = reporter || function() {}
var data = new FormData()
data.append('file', bannerFile)
/*data.append('preview', JSON.stringify({
"out": "base64",
"format": "avif",
"resize": {
"width": 360,
"height": 203,
"fit": "cover",
"withoutEnlargement": true,
"kernel": "mitchell"
},
"avif": {
"quality": 40,
"effort": 9
}
}))*/
data.append('medium', JSON.stringify({
"format": "avif",
"resize": {
"width": 1280,
"height": 720,
"fit": "cover",
"withoutEnlargement": true,
"kernel": "mitchell"
},
"avif": {
"quality": 75,
"effort": 3
}
}))
report(lang.api_banner_upload)
return api.sendRequest({
method: 'POST',
url: token.resize + '?token=' + token.token,
body: data,
})
.then(async (banner) => {
let preview = null
let quality = 60
while (!preview && quality > 10) {
report(lang.format(lang.api_banner_generate, quality))
let check = await api.sendRequest({
method: 'POST',
url: token.resize + '/' + banner.filename + '?token=' + token.token,
body: {
"preview": {
"out": "base64",
"format": "avif",
"resize": {
"width": 360,
"height": 203,
"fit": "cover",
"withoutEnlargement": true,
"kernel": "mitchell"
},
"avif": {
"quality": quality,
"effort": 9
}
},
},
})
if (check.preview.base64.length < 8000) {
preview = check.preview.base64
} else {
quality -= 5
}
}
report(null)
api.sendRequest({
method: 'DELETE',
url: token.delete + banner.filename + '?token=' + token.token,
}).catch(err => console.error(err))
return {
file: bannerFile,
size: bannerFile.size,
medium: {
filename: banner.medium.filename,
path: banner.medium.path,
},
preview: {
base64: preview,
},
}
})
.then(
function(res) {
clearLoading(request)
return res
},
function(err) {
clearLoading(request)
return Promise.reject(err)
}
)
},
prettyFormatBytes: function(bytes) {
if (bytes < 1024) {
return `${bytes} B`
}
if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`
}
if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
},
uploadFileProgress: function(options, file, reporter) {
let request = requestIndex++
requests.add(request)
api.loading = true
return new Promise(function(res, rej) {
let report = reporter || function() {}
let formdata = new FormData()
formdata.append('file', file)
let request = new XMLHttpRequest()
let finished = false
let lastMarker = new Date()
let lastMarkerLoaded = 0
let lastMarkerSpeed = '...'
request.abortRequest = function() {
finished = true
request.abort()
}
request.upload.addEventListener('progress', function (e) {
let check = new Date()
if (check - lastMarker >= 1000) {
let loaded = e.loaded - lastMarkerLoaded
lastMarkerSpeed = api.prettyFormatBytes(loaded / ((check - lastMarker) / 1000))
lastMarker = check
lastMarkerLoaded = e.loaded
}
report(request, Math.min(e.loaded / file.size * 100, 100), lastMarkerSpeed)
})
request.addEventListener('abort', function(e) {
finished = true
window.requestAnimationFrame(m.redraw.bind(m))
rej()
})
request.addEventListener('readystatechange', function(e) {
if (finished) return
if (request.readyState !== 4) return
finished = true
let out = safeParseReponse(request.responseText, request.status, options.url)
if (out instanceof Error || request.status >= 300) {
return rej(out)
}
return res(out)
})
request.open(options.method || 'POST', options.url)
request.send(formdata)
report(request, 0)
})
.then(function(res) {
clearLoading(request)
return res
}, function(err) {
clearLoading(request)
return Promise.reject(err)
})
},
}
module.exports = api