2022-12-26 05:59:44 +13:00
|
|
|
import {
|
2023-01-13 04:50:09 +13:00
|
|
|
accountReservationSingleUrl,
|
|
|
|
accountReservationUrl,
|
2022-12-26 05:59:44 +13:00
|
|
|
accountPasswordUrl,
|
|
|
|
accountSettingsUrl,
|
|
|
|
accountSubscriptionSingleUrl,
|
|
|
|
accountSubscriptionUrl,
|
|
|
|
accountTokenUrl,
|
2023-01-12 15:38:10 +13:00
|
|
|
accountUrl, maybeWithAuth, topicUrl,
|
2022-12-27 15:27:07 +13:00
|
|
|
withBasicAuth,
|
2023-01-16 17:29:46 +13:00
|
|
|
withBearerAuth, accountBillingSubscriptionUrl, accountBillingPortalUrl
|
2022-12-26 05:59:44 +13:00
|
|
|
} from "./utils";
|
|
|
|
import session from "./Session";
|
2022-12-26 07:42:44 +13:00
|
|
|
import subscriptionManager from "./SubscriptionManager";
|
2023-01-03 16:21:11 +13:00
|
|
|
import i18n from "i18next";
|
|
|
|
import prefs from "./Prefs";
|
|
|
|
import routes from "../components/routes";
|
2023-01-12 15:38:10 +13:00
|
|
|
import userManager from "./UserManager";
|
2022-12-26 07:42:44 +13:00
|
|
|
|
|
|
|
const delayMillis = 45000; // 45 seconds
|
|
|
|
const intervalMillis = 900000; // 15 minutes
|
2022-12-26 05:59:44 +13:00
|
|
|
|
|
|
|
class AccountApi {
|
2022-12-26 07:42:44 +13:00
|
|
|
constructor() {
|
|
|
|
this.timer = null;
|
2023-01-03 16:21:11 +13:00
|
|
|
this.listener = null; // Fired when account is fetched from remote
|
2023-01-12 15:38:10 +13:00
|
|
|
|
|
|
|
// Random ID used to identify this client when sending/receiving "sync" events
|
|
|
|
// to the sync topic of an account. This ID doesn't matter much, but it will prevent
|
|
|
|
// a client from reacting to its own message.
|
|
|
|
this.identity = Math.floor(Math.random() * 2586000);
|
2023-01-03 16:21:11 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
registerListener(listener) {
|
|
|
|
this.listener = listener;
|
|
|
|
}
|
|
|
|
|
|
|
|
resetListener() {
|
|
|
|
this.listener = null;
|
2022-12-26 07:42:44 +13:00
|
|
|
}
|
|
|
|
|
2022-12-26 05:59:44 +13:00
|
|
|
async login(user) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountTokenUrl(config.base_url);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Checking auth for ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBasicAuth({}, user.username, user.password)
|
2022-12-26 05:59:44 +13:00
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
const json = await response.json();
|
|
|
|
if (!json.token) {
|
|
|
|
throw new Error(`Unexpected server response: Cannot find token`);
|
|
|
|
}
|
|
|
|
return json.token;
|
|
|
|
}
|
|
|
|
|
2022-12-29 09:51:09 +13:00
|
|
|
async logout() {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountTokenUrl(config.base_url);
|
2022-12-29 09:51:09 +13:00
|
|
|
console.log(`[AccountApi] Logging out from ${url} using token ${session.token()}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "DELETE",
|
2022-12-29 09:51:09 +13:00
|
|
|
headers: withBearerAuth({}, session.token())
|
2022-12-26 05:59:44 +13:00
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async create(username, password) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountUrl(config.base_url);
|
2022-12-26 05:59:44 +13:00
|
|
|
const body = JSON.stringify({
|
|
|
|
username: username,
|
|
|
|
password: password
|
|
|
|
});
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Creating user account ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
|
|
|
body: body
|
|
|
|
});
|
|
|
|
if (response.status === 409) {
|
|
|
|
throw new UsernameTakenError(username);
|
|
|
|
} else if (response.status === 429) {
|
|
|
|
throw new AccountCreateLimitReachedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async get() {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountUrl(config.base_url);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Fetching user account ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token())
|
2022-12-26 05:59:44 +13:00
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
const account = await response.json();
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Account`, account);
|
2023-01-03 16:21:11 +13:00
|
|
|
if (this.listener) {
|
|
|
|
this.listener(account);
|
|
|
|
}
|
2022-12-26 05:59:44 +13:00
|
|
|
return account;
|
|
|
|
}
|
|
|
|
|
|
|
|
async delete() {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountUrl(config.base_url);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Deleting user account ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "DELETE",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token())
|
2022-12-26 05:59:44 +13:00
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async changePassword(newPassword) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountPasswordUrl(config.base_url);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Changing account password ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token()),
|
2022-12-26 05:59:44 +13:00
|
|
|
body: JSON.stringify({
|
|
|
|
password: newPassword
|
|
|
|
})
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async extendToken() {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountTokenUrl(config.base_url);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Extending user access token ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "PATCH",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token())
|
2022-12-26 05:59:44 +13:00
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateSettings(payload) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountSettingsUrl(config.base_url);
|
2022-12-26 05:59:44 +13:00
|
|
|
const body = JSON.stringify(payload);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Updating user account ${url}: ${body}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "PATCH",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token()),
|
2022-12-26 05:59:44 +13:00
|
|
|
body: body
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
2023-01-12 15:38:10 +13:00
|
|
|
this.triggerChange(); // Dangle!
|
2022-12-26 05:59:44 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
async addSubscription(payload) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountSubscriptionUrl(config.base_url);
|
2022-12-26 05:59:44 +13:00
|
|
|
const body = JSON.stringify(payload);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Adding user subscription ${url}: ${body}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token()),
|
2022-12-26 05:59:44 +13:00
|
|
|
body: body
|
|
|
|
});
|
2022-12-26 16:29:55 +13:00
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
const subscription = await response.json();
|
|
|
|
console.log(`[AccountApi] Subscription`, subscription);
|
2023-01-12 15:38:10 +13:00
|
|
|
this.triggerChange(); // Dangle!
|
2022-12-26 16:29:55 +13:00
|
|
|
return subscription;
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateSubscription(remoteId, payload) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountSubscriptionSingleUrl(config.base_url, remoteId);
|
2022-12-26 16:29:55 +13:00
|
|
|
const body = JSON.stringify(payload);
|
|
|
|
console.log(`[AccountApi] Updating user subscription ${url}: ${body}`);
|
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "PATCH",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token()),
|
2022-12-26 16:29:55 +13:00
|
|
|
body: body
|
|
|
|
});
|
2022-12-26 05:59:44 +13:00
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
const subscription = await response.json();
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Subscription`, subscription);
|
2023-01-12 15:38:10 +13:00
|
|
|
this.triggerChange(); // Dangle!
|
2022-12-26 05:59:44 +13:00
|
|
|
return subscription;
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteSubscription(remoteId) {
|
2023-01-05 16:47:12 +13:00
|
|
|
const url = accountSubscriptionSingleUrl(config.base_url, remoteId);
|
2022-12-26 07:42:44 +13:00
|
|
|
console.log(`[AccountApi] Removing user subscription ${url}`);
|
2022-12-26 05:59:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "DELETE",
|
2022-12-27 15:27:07 +13:00
|
|
|
headers: withBearerAuth({}, session.token())
|
2022-12-26 05:59:44 +13:00
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
2023-01-12 15:38:10 +13:00
|
|
|
this.triggerChange(); // Dangle!
|
2022-12-26 05:59:44 +13:00
|
|
|
}
|
2022-12-26 07:42:44 +13:00
|
|
|
|
2023-01-15 00:43:44 +13:00
|
|
|
async upsertReservation(topic, everyone) {
|
2023-01-13 04:50:09 +13:00
|
|
|
const url = accountReservationUrl(config.base_url);
|
2023-01-03 15:52:20 +13:00
|
|
|
console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`);
|
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
|
|
|
headers: withBearerAuth({}, session.token()),
|
|
|
|
body: JSON.stringify({
|
|
|
|
topic: topic,
|
|
|
|
everyone: everyone
|
|
|
|
})
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
2023-01-05 14:34:22 +13:00
|
|
|
} else if (response.status === 409) {
|
|
|
|
throw new TopicReservedError();
|
2023-01-03 15:52:20 +13:00
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
2023-01-12 15:38:10 +13:00
|
|
|
this.triggerChange(); // Dangle!
|
2023-01-03 15:52:20 +13:00
|
|
|
}
|
|
|
|
|
2023-01-15 00:43:44 +13:00
|
|
|
async deleteReservation(topic) {
|
2023-01-13 04:50:09 +13:00
|
|
|
const url = accountReservationSingleUrl(config.base_url, topic);
|
2023-01-03 15:52:20 +13:00
|
|
|
console.log(`[AccountApi] Removing topic reservation ${url}`);
|
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "DELETE",
|
|
|
|
headers: withBearerAuth({}, session.token())
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
2023-01-12 15:38:10 +13:00
|
|
|
this.triggerChange(); // Dangle!
|
2023-01-03 15:52:20 +13:00
|
|
|
}
|
|
|
|
|
2023-01-16 17:29:46 +13:00
|
|
|
async updateBillingSubscription(tier) {
|
|
|
|
const url = accountBillingSubscriptionUrl(config.base_url);
|
|
|
|
console.log(`[AccountApi] Requesting tier change to ${tier}`);
|
2023-01-15 00:43:44 +13:00
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
|
|
|
headers: withBearerAuth({}, session.token()),
|
|
|
|
body: JSON.stringify({
|
|
|
|
tier: tier
|
|
|
|
})
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
return await response.json();
|
|
|
|
}
|
|
|
|
|
2023-01-16 17:29:46 +13:00
|
|
|
async deleteBillingSubscription() {
|
|
|
|
const url = accountBillingSubscriptionUrl(config.base_url);
|
|
|
|
console.log(`[AccountApi] Cancelling paid subscription`);
|
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "DELETE",
|
|
|
|
headers: withBearerAuth({}, session.token())
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-15 00:43:44 +13:00
|
|
|
async createBillingPortalSession() {
|
|
|
|
const url = accountBillingPortalUrl(config.base_url);
|
|
|
|
console.log(`[AccountApi] Creating billing portal session`);
|
|
|
|
const response = await fetch(url, {
|
|
|
|
method: "POST",
|
|
|
|
headers: withBearerAuth({}, session.token())
|
|
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
throw new UnauthorizedError();
|
|
|
|
} else if (response.status !== 200) {
|
|
|
|
throw new Error(`Unexpected server response ${response.status}`);
|
|
|
|
}
|
|
|
|
return await response.json();
|
|
|
|
}
|
|
|
|
|
2023-01-03 16:21:11 +13:00
|
|
|
async sync() {
|
|
|
|
try {
|
|
|
|
if (!session.token()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
console.log(`[AccountApi] Syncing account`);
|
2023-01-04 05:28:04 +13:00
|
|
|
const account = await this.get();
|
|
|
|
if (account.language) {
|
|
|
|
await i18n.changeLanguage(account.language);
|
2023-01-03 16:21:11 +13:00
|
|
|
}
|
2023-01-04 05:28:04 +13:00
|
|
|
if (account.notification) {
|
|
|
|
if (account.notification.sound) {
|
|
|
|
await prefs.setSound(account.notification.sound);
|
2023-01-03 16:21:11 +13:00
|
|
|
}
|
2023-01-04 05:28:04 +13:00
|
|
|
if (account.notification.delete_after) {
|
|
|
|
await prefs.setDeleteAfter(account.notification.delete_after);
|
2023-01-03 16:21:11 +13:00
|
|
|
}
|
2023-01-04 05:28:04 +13:00
|
|
|
if (account.notification.min_priority) {
|
|
|
|
await prefs.setMinPriority(account.notification.min_priority);
|
2023-01-03 16:21:11 +13:00
|
|
|
}
|
|
|
|
}
|
2023-01-04 05:28:04 +13:00
|
|
|
if (account.subscriptions) {
|
|
|
|
await subscriptionManager.syncFromRemote(account.subscriptions, account.reservations);
|
2023-01-03 16:21:11 +13:00
|
|
|
}
|
2023-01-04 05:28:04 +13:00
|
|
|
return account;
|
2023-01-03 16:21:11 +13:00
|
|
|
} catch (e) {
|
|
|
|
console.log(`[AccountApi] Error fetching account`, e);
|
|
|
|
if ((e instanceof UnauthorizedError)) {
|
|
|
|
session.resetAndRedirect(routes.login);
|
|
|
|
}
|
|
|
|
}
|
2022-12-27 15:27:07 +13:00
|
|
|
}
|
|
|
|
|
2023-01-12 15:38:10 +13:00
|
|
|
async triggerChange() {
|
|
|
|
const account = await this.get();
|
|
|
|
if (!account.sync_topic) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const url = topicUrl(config.base_url, account.sync_topic);
|
|
|
|
console.log(`[AccountApi] Triggering account change to ${url}`);
|
|
|
|
const user = await userManager.get(config.base_url);
|
|
|
|
const headers = {
|
|
|
|
Cache: "no" // We really don't need to store this!
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
const response = await fetch(url, {
|
|
|
|
method: 'PUT',
|
|
|
|
body: JSON.stringify({
|
|
|
|
event: "sync",
|
|
|
|
source: this.identity
|
|
|
|
}),
|
|
|
|
headers: maybeWithAuth(headers, user)
|
|
|
|
});
|
|
|
|
if (response.status < 200 || response.status > 299) {
|
|
|
|
throw new Error(`Unexpected response: ${response.status}`);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[AccountApi] Publishing to sync topic failed`, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-26 07:42:44 +13:00
|
|
|
startWorker() {
|
|
|
|
if (this.timer !== null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
console.log(`[AccountApi] Starting worker`);
|
|
|
|
this.timer = setInterval(() => this.runWorker(), intervalMillis);
|
|
|
|
setTimeout(() => this.runWorker(), delayMillis);
|
|
|
|
}
|
|
|
|
|
|
|
|
async runWorker() {
|
|
|
|
if (!session.token()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
console.log(`[AccountApi] Extending user access token`);
|
|
|
|
try {
|
|
|
|
await this.extendToken();
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[AccountApi] Error extending user access token`, e);
|
|
|
|
}
|
|
|
|
}
|
2022-12-26 05:59:44 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
export class UsernameTakenError extends Error {
|
|
|
|
constructor(username) {
|
|
|
|
super("Username taken");
|
|
|
|
this.username = username;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-05 14:34:22 +13:00
|
|
|
export class TopicReservedError extends Error {
|
|
|
|
constructor(topic) {
|
|
|
|
super("Topic already reserved");
|
|
|
|
this.topic = topic;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-26 05:59:44 +13:00
|
|
|
export class AccountCreateLimitReachedError extends Error {
|
|
|
|
constructor() {
|
|
|
|
super("Account creation limit reached");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class UnauthorizedError extends Error {
|
|
|
|
constructor() {
|
|
|
|
super("Unauthorized");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const accountApi = new AccountApi();
|
|
|
|
export default accountApi;
|