szurubooru/client/js/api.js

470 lines
14 KiB
JavaScript
Raw Normal View History

"use strict";
const cookies = require("js-cookie");
const request = require("superagent");
const events = require("./events.js");
const progress = require("./util/progress.js");
const uri = require("./util/uri.js");
let fileTokens = {};
let remoteConfig = null;
class Api extends events.EventTarget {
2016-03-30 20:45:37 +02:00
constructor() {
super();
this.user = null;
2016-03-30 20:45:37 +02:00
this.userName = null;
this.userPassword = null;
this.token = null;
this.cache = {};
2016-05-08 16:59:25 +02:00
this.allRanks = [
"anonymous",
"restricted",
"regular",
"power",
"moderator",
"administrator",
"nobody",
2016-05-08 16:59:25 +02:00
];
this.rankNames = new Map([
["anonymous", "Anonymous"],
["restricted", "Restricted user"],
["regular", "Regular user"],
["power", "Power user"],
["moderator", "Moderator"],
["administrator", "Administrator"],
["nobody", "Nobody"],
]);
2016-03-30 20:45:37 +02:00
}
get(url, options) {
if (url in this.cache) {
return new Promise((resolve, reject) => {
resolve(this.cache[url]);
});
}
return this._wrappedRequest(url, request.get, {}, {}, options).then(
(response) => {
this.cache[url] = response;
return Promise.resolve(response);
}
);
}
post(url, data, files, options) {
this.cache = {};
2017-01-07 14:28:36 +01:00
return this._wrappedRequest(url, request.post, data, files, options);
}
put(url, data, files, options) {
this.cache = {};
2017-01-07 14:28:36 +01:00
return this._wrappedRequest(url, request.put, data, files, options);
}
delete(url, data, options) {
this.cache = {};
2017-01-07 14:28:36 +01:00
return this._wrappedRequest(url, request.delete, data, {}, options);
}
fetchConfig() {
if (remoteConfig === null) {
return this.get(uri.formatApiLink("info")).then((response) => {
remoteConfig = response.config;
});
} else {
return Promise.resolve();
}
}
getName() {
return remoteConfig.name;
}
getTagNameRegex() {
return remoteConfig.tagNameRegex;
}
2020-05-04 04:53:28 +02:00
getPoolNameRegex() {
return remoteConfig.poolNameRegex;
}
getPasswordRegex() {
return remoteConfig.passwordRegex;
}
getUserNameRegex() {
return remoteConfig.userNameRegex;
}
getContactEmail() {
return remoteConfig.contactEmail;
}
canSendMails() {
return !!remoteConfig.canSendMails;
}
safetyEnabled() {
return !!remoteConfig.enableSafety;
}
hasPrivilege(lookup) {
let minViableRank = null;
for (let p of Object.keys(remoteConfig.privileges)) {
if (!p.startsWith(lookup)) {
continue;
}
const rankIndex = this.allRanks.indexOf(
remoteConfig.privileges[p]
);
if (minViableRank === null || rankIndex < minViableRank) {
minViableRank = rankIndex;
}
}
if (minViableRank === null) {
throw `Bad privilege name: ${lookup}`;
}
let myRank =
this.user !== null ? this.allRanks.indexOf(this.user.rank) : 0;
return myRank >= minViableRank;
2016-03-30 20:45:37 +02:00
}
loginFromCookies() {
const auth = cookies.getJSON("auth");
return auth && auth.user && auth.token
? this.loginWithToken(auth.user, auth.token, true)
: Promise.resolve();
}
loginWithToken(userName, token, doRemember) {
this.cache = {};
2016-03-30 21:01:18 +02:00
return new Promise((resolve, reject) => {
this.userName = userName;
this.token = token;
this.get("/user/" + userName + "?bump-login=true").then(
(response) => {
const options = {};
if (doRemember) {
options.expires = 365;
}
cookies.set(
"auth",
{ user: userName, token: token },
options
);
this.user = response;
resolve();
this.dispatchEvent(new CustomEvent("login"));
},
(error) => {
reject(error);
this.logout();
}
);
});
}
createToken(userName, options) {
let userTokenRequest = {
enabled: true,
note: "Web Login Token",
};
if (typeof options.expires !== "undefined") {
userTokenRequest.expirationTime = new Date()
.addDays(options.expires)
.toISOString();
}
return new Promise((resolve, reject) => {
this.post("/user-token/" + userName, userTokenRequest).then(
(response) => {
cookies.set(
"auth",
{ user: userName, token: response.token },
options
);
this.userName = userName;
this.token = response.token;
this.userPassword = null;
},
(error) => {
reject(error);
}
);
});
}
deleteToken(userName, userToken) {
return new Promise((resolve, reject) => {
this.delete("/user-token/" + userName + "/" + userToken, {}).then(
(response) => {
const options = {};
cookies.set(
"auth",
{ user: userName, token: null },
options
);
resolve();
},
(error) => {
reject(error);
}
);
});
}
login(userName, userPassword, doRemember) {
this.cache = {};
return new Promise((resolve, reject) => {
this.userName = userName;
this.userPassword = userPassword;
this.get("/user/" + userName + "?bump-login=true").then(
(response) => {
const options = {};
if (doRemember) {
options.expires = 365;
}
this.createToken(this.userName, options);
2016-05-30 22:20:42 +02:00
this.user = response;
resolve();
this.dispatchEvent(new CustomEvent("login"));
},
(error) => {
2017-01-08 02:12:38 +01:00
reject(error);
2016-03-30 21:01:18 +02:00
this.logout();
}
);
2016-03-30 21:01:18 +02:00
});
2016-03-30 20:45:37 +02:00
}
logout() {
let self = this;
this.deleteToken(this.userName, this.token).then(
(response) => {
self._logout();
},
(error) => {
self._logout();
}
);
}
_logout() {
this.user = null;
2016-03-30 20:45:37 +02:00
this.userName = null;
this.userPassword = null;
this.token = null;
this.dispatchEvent(new CustomEvent("logout"));
2016-03-30 20:45:37 +02:00
}
forget() {
cookies.remove("auth");
}
isLoggedIn(user) {
if (user) {
return (
this.userName !== null &&
this.userName.toLowerCase() === user.name.toLowerCase()
);
} else {
return this.userName !== null;
}
2016-03-30 20:45:37 +02:00
}
isCurrentAuthToken(userToken) {
return userToken.token === this.token;
}
_getFullUrl(url) {
const fullUrl = ("api/" + url).replace(/([^:])\/+/g, "$1/");
const matches = fullUrl.match(/^([^?]*)\??(.*)$/);
const baseUrl = matches[1];
const request = matches[2];
return [baseUrl, request];
}
2017-01-07 14:28:36 +01:00
_getFileId(file) {
if (file.constructor === String) {
return file;
}
return file.name + file.size;
}
2017-01-07 14:28:36 +01:00
_wrappedRequest(url, requestFactory, data, files, options) {
// transform the request: upload each file, then make the request use
// its tokens.
data = Object.assign({}, data);
let abortFunction = () => {};
2017-01-07 16:24:56 +01:00
let promise = Promise.resolve();
2017-01-07 14:28:36 +01:00
if (files) {
for (let key of Object.keys(files)) {
const file = files[key];
const fileId = this._getFileId(file);
if (fileTokens[fileId]) {
data[key + "Token"] = fileTokens[fileId];
2017-01-07 14:28:36 +01:00
} else {
promise = promise
.then(() => {
2017-01-07 16:24:56 +01:00
let uploadPromise = this._upload(file);
abortFunction = () => uploadPromise.abort();
return uploadPromise;
2017-01-07 14:28:36 +01:00
})
.then((token) => {
2017-01-07 14:28:36 +01:00
abortFunction = () => {};
fileTokens[fileId] = token;
data[key + "Token"] = token;
2017-01-07 14:28:36 +01:00
return Promise.resolve();
});
}
}
}
promise = promise
.then(() => {
2017-01-07 16:24:56 +01:00
let requestPromise = this._rawRequest(
url,
requestFactory,
data,
{},
options
);
2017-01-07 16:24:56 +01:00
abortFunction = () => requestPromise.abort();
return requestPromise;
2017-01-08 11:04:21 +01:00
})
.catch((error) => {
if (
error.response &&
error.response.name === "MissingOrExpiredRequiredFileError"
) {
2017-01-08 11:04:21 +01:00
for (let key of Object.keys(files)) {
const file = files[key];
const fileId = this._getFileId(file);
fileTokens[fileId] = null;
2017-01-08 11:04:21 +01:00
}
error.message =
"The uploaded file has expired; " +
"please resend the form to reupload.";
2017-01-08 11:04:21 +01:00
}
2017-01-07 16:24:56 +01:00
return Promise.reject(error);
});
2017-01-07 14:28:36 +01:00
promise.abort = () => abortFunction();
return promise;
}
_upload(file, options) {
let abortFunction = () => {};
let returnedPromise = new Promise((resolve, reject) => {
2017-01-07 16:24:56 +01:00
let uploadPromise = this._rawRequest(
"uploads",
request.post,
{},
{ content: file },
options
);
2017-01-07 16:24:56 +01:00
abortFunction = () => uploadPromise.abort();
return uploadPromise.then((response) => {
abortFunction = () => {};
return resolve(response.token);
}, reject);
2017-01-07 14:28:36 +01:00
});
returnedPromise.abort = () => abortFunction();
return returnedPromise;
}
_rawRequest(url, requestFactory, data, files, options) {
options = options || {};
data = Object.assign({}, data);
const [fullUrl, query] = this._getFullUrl(url);
2017-01-07 16:24:56 +01:00
let abortFunction = () => {};
let returnedPromise = new Promise((resolve, reject) => {
2017-01-07 14:28:36 +01:00
let req = requestFactory(fullUrl);
req.set("Accept", "application/json");
2017-01-07 14:28:36 +01:00
if (query) {
req.query(query);
}
if (files) {
for (let key of Object.keys(files)) {
const value = files[key];
if (value.constructor === String) {
data[key + "Url"] = value;
2017-01-07 14:28:36 +01:00
} else {
req.attach(key, value || new Blob());
}
}
}
if (data) {
if (files && Object.keys(files).length) {
req.attach("metadata", new Blob([JSON.stringify(data)]));
2017-01-07 14:28:36 +01:00
} else {
req.set("Content-Type", "application/json");
2017-01-07 14:28:36 +01:00
req.send(data);
}
}
try {
if (this.userName && this.token) {
req.auth = null;
2020-06-04 20:09:35 +02:00
// eslint-disable-next-line no-undef
req.set(
"Authorization",
"Token " +
new Buffer(
this.userName + ":" + this.token
).toString("base64")
);
} else if (this.userName && this.userPassword) {
2017-01-07 14:28:36 +01:00
req.auth(
this.userName,
encodeURIComponent(this.userPassword).replace(
/%([0-9A-F]{2})/g,
(match, p1) => {
return String.fromCharCode("0x" + p1);
}
)
);
2017-01-07 14:28:36 +01:00
}
} catch (e) {
2017-01-08 02:12:38 +01:00
reject(
new Error("Authentication error (malformed credentials)")
);
2017-01-07 14:28:36 +01:00
}
if (!options.noProgress) {
progress.start();
2017-01-07 14:28:36 +01:00
}
abortFunction = () => {
req.abort(); // does *NOT* call the callback passed in .end()
progress.done();
2017-01-08 02:12:38 +01:00
reject(
new Error("The request was aborted due to user cancel.")
);
2017-01-07 14:28:36 +01:00
};
req.end((error, response) => {
progress.done();
2017-01-07 16:24:56 +01:00
abortFunction = () => {};
2017-01-07 14:28:36 +01:00
if (error) {
2017-01-08 02:12:38 +01:00
if (response && response.body) {
error = new Error(
response.body.description || "Unknown error"
);
2017-01-08 02:12:38 +01:00
error.response = response.body;
}
reject(error);
2017-01-07 14:28:36 +01:00
} else {
resolve(response.body);
}
});
});
2017-01-07 16:24:56 +01:00
returnedPromise.abort = () => abortFunction();
return returnedPromise;
2017-01-07 14:28:36 +01:00
}
}
module.exports = new Api();