You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
4.1 KiB

import { TokenStore } from "@client/state/TokenStore";
import ApiError from "@common/ApiError";
import { ErrorCode } from "@common/ErrorCode";
7 years ago
import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi";
import log from "@common/logger";
import { DefaultEvents, TypedEventEmitter } from "@common/TypedEventEmitter";
import { runInAction } from "mobx";
export { ApiError };
interface HttpApiEvents extends DefaultEvents {
error(err: ApiError): void;
tokenError(err: ApiError): void;
}
export default class HttpApi extends TypedEventEmitter<HttpApiEvents> {
baseUrl: string;
tokenStore: TokenStore;
private get authorizationHeader(): {} | { "Authorization": string } {
if (!this.tokenStore.accessToken) {
return {};
}
return { Authorization: `Bearer ${this.tokenStore.accessToken.token}` };
}
constructor(baseUrl: string = `${location.protocol}//${location.hostname}:${location.port}/api`) {
super();
while (baseUrl.charAt(baseUrl.length - 1) === "/") {
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
}
this.baseUrl = baseUrl;
7 years ago
this.tokenStore = new TokenStore();
this.on("error", (err: ApiError) => {
if (err.code === ErrorCode.BadToken) {
this.emit("tokenError", err);
}
});
}
async makeRequest(url: string, options?: RequestInit, body?: any): Promise<any> {
try {
options = options || {};
options = {
headers: {
"Content-Type": "application/json",
...this.authorizationHeader,
...options.headers || {},
},
body: JSON.stringify(body),
...options,
};
let response: Response;
try {
response = await fetch(this.baseUrl + url, options);
} catch (err) {
throw new ApiError("Http request error", ErrorCode.Internal, err);
}
let responseBody: any;
try {
responseBody = await response.json() || {};
} catch (e) {
throw new ApiError("Invalid JSON response", ErrorCode.Internal, e);
}
if (!response.ok) {
throw new ApiError(responseBody.message || response.statusText, responseBody.code, responseBody.data);
}
return responseBody;
} catch (err) {
this.emit("error", err);
throw err;
}
}
7 years ago
async grantPassword(username: string, password: string) {
const request: TokenGrantPasswordRequest = {
grant_type: "password", username, password,
};
const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
method: "POST",
}, request);
runInAction("grantPasswordSuccess", () => {
this.tokenStore.accessToken.token = response.access_token;
this.tokenStore.refreshToken.token = response.refresh_token;
this.tokenStore.saveLocalStorage();
});
7 years ago
const { accessToken } = this.tokenStore;
log.debug({ aud: accessToken.claims!.aud }, "got password grant tokens");
}
async grantRefresh() {
const { refreshToken } = this.tokenStore;
if (!refreshToken.isValid) {
throw new ApiError("can not grant refresh with invalid refresh_token");
}
const request: TokenGrantRefreshRequest = {
grant_type: "refresh", refresh_token: refreshToken.token!,
};
const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
method: "POST",
}, request);
runInAction("grantRefreshSuccess", () => {
this.tokenStore.accessToken.token = response.access_token;
this.tokenStore.refreshToken.token = response.refresh_token;
this.tokenStore.saveLocalStorage();
});
7 years ago
const { accessToken } = this.tokenStore;
log.debug({ aud: accessToken.claims!.aud }, "got refresh grant tokens");
}
}