added authorization stores and fixed http client

This commit is contained in:
Fran Jurmanović
2021-05-29 17:11:21 +02:00
parent f835c76b39
commit f0d2e7b06d
23 changed files with 473 additions and 24 deletions

View File

@@ -2,16 +2,24 @@ import { attr, targets, controller, target } from "@github/catalyst";
import { closest, index, update, isTrue } from "core/utils";
import { html, render, until } from "@github/jtml";
import { PingService } from "services/";
import { RouterService } from "core/services";
import { AppService, HttpClient, RouterService } from "core/services";
import { AuthStore } from "core/store";
@controller
class AppMainElement extends HTMLElement {
public routerService: RouterService;
public authStore: AuthStore;
private httpClient: HttpClient;
public appService: AppService;
constructor() {
super();
}
connectedCallback() {
this.httpClient = new HttpClient();
this.appService = new AppService(this, this.httpClient);
this.routerService = new RouterService(this);
this.authStore = new AuthStore(this.appService);
this.routerService.setRoutes([
{
path: "/",
@@ -21,10 +29,30 @@ class AppMainElement extends HTMLElement {
{
path: "/home",
component: "home-page",
middleware: this.isAuth,
},
{
path: "/rb",
component: "register-page",
},
{
path: "/unauthorized",
component: "register-page",
},
{
path: "token-expired",
component: "register-page",
},
]);
this.routerService.init();
}
isAuth = () => {
if (this.authStore?.token == null) {
this.routerService.goTo("/unauthorized");
return true;
}
};
}
export type { AppMainElement };

View File

@@ -1,2 +1,3 @@
export * from "./app-main/AppMainElement";
export * from "./app-link/AppLinkElement";
export * from "./input-field/InputFieldElement";

View File

@@ -0,0 +1,81 @@
import { attr, targets, controller, target } from "@github/catalyst";
import { closest, index, update, isTrue, firstUpper } from "core/utils";
import { html, render, until } from "@github/jtml";
import { PingService } from "services/";
import { AppMainElement } from "components/app-main/AppMainElement";
import { RouterService } from "core/services";
import randomId from "core/utils/random-id";
import validator from "validator";
import { validatorErrors } from "core/constants";
@controller
class InputFieldElement extends HTMLElement {
@closest appMain: AppMainElement;
@attr name: string;
@attr type: string;
@attr label: string;
@attr rules: string;
@target main: HTMLElement;
@target inp: HTMLElement;
error: string;
randId: string;
routerService: RouterService;
constructor() {
super();
}
@update
public connectedCallback(): void {
this.randId = `${name}${randomId()}`;
}
get valid(): boolean {
return !!this.error;
}
@update
validate(): boolean {
let _return = true;
const rules = this.rules?.split("|").filter((a) => a);
const value = (this.inp as HTMLInputElement)?.value;
rules
.slice()
.reverse()
.forEach((rule) => {
let valid = true;
if (rule == "required") {
if (value === "") valid = false;
} else {
if (validator.hasOwnProperty(rule)) {
valid = validator?.[rule]?.(value);
}
}
if (!valid) {
const error = validatorErrors[rule]?.replaceAll(
"{- name}",
firstUpper(this.name?.toString())
);
_return = false;
this.error = error;
}
});
if (_return) {
this.error = null;
}
return _return;
}
update() {
render(
html`<span data-target="input-field.main">
${this.label &&
html`<label for="${this.randId}">${this.label}</label>`}
<input type="${this.type}" data-target="input-field.inp" />
${this.error && html`<span>${this.error}</span>`}
</span>`,
this
);
}
}
export type { InputFieldElement };

View File

@@ -0,0 +1 @@
export * from "./validatorErrors";

View File

@@ -0,0 +1,4 @@
export const validatorErrors = {
required: "{- name} is required.",
isEmail: "{- name} needs to be email format.",
};

View File

@@ -0,0 +1,135 @@
import { AppMainElement } from "components/";
import { HttpClient } from "..";
class AppService {
constructor(
public appMain: AppMainElement,
public httpClient: HttpClient
) {}
post = async (
url: string,
data: Object,
headersParam: HeadersInit
): Promise<any> => {
headersParam = {
...headersParam,
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
};
try {
const response = await this.httpClient.post(
url,
data,
headersParam
);
if (
response?.statusCode == 400 ||
response?.statusCode == 500 ||
response?.statusCode == 401
) {
if (response?.statusCode == 401) {
this.appMain.authStore.token = null;
this.appMain.routerService.goTo("/token-expired");
}
throw response;
}
return response;
} catch (err) {
throw err;
}
};
put = async (
url: string,
data: Object,
headersParam: HeadersInit
): Promise<any> => {
headersParam = {
...headersParam,
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
};
try {
const response = await this.httpClient.put(url, data, headersParam);
if (
response?.statusCode == 400 ||
response?.statusCode == 500 ||
response?.statusCode == 401
) {
if (response?.statusCode == 401) {
this.appMain.authStore.token = null;
this.appMain.routerService.goTo("/token-expired");
}
throw response;
}
return response;
} catch (err) {
throw err;
}
};
delete = async (
url: string,
data: Object,
headersParam: HeadersInit
): Promise<any> => {
headersParam = {
...headersParam,
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
};
try {
const response = await this.httpClient.delete(
url,
data,
headersParam
);
if (
response?.statusCode == 400 ||
response?.statusCode == 500 ||
response?.statusCode == 401
) {
if (response?.statusCode == 401) {
this.appMain.authStore.token = null;
this.appMain.routerService.goTo("/token-expired");
}
throw response;
}
return response;
} catch (err) {
throw err;
}
};
get = async (
url: string,
params: Object,
headersParam: HeadersInit
): Promise<any> => {
headersParam = {
...headersParam,
Authorization: `BEARER ${this.appMain?.authStore?.token}`,
};
try {
const response = await this.httpClient.get(
url,
params,
headersParam
);
if (
response?.statusCode == 400 ||
response?.statusCode == 500 ||
response?.statusCode == 401
) {
if (response?.statusCode == 401) {
this.appMain.authStore.token = null;
this.appMain.routerService.goTo("/token-expired");
}
throw response;
}
return response;
} catch (err) {
throw err;
}
};
}
export default AppService;

View File

@@ -1,29 +1,26 @@
import { HttpClient } from "core/services";
import { AppService, HttpClient } from "core/services";
class BaseService {
private httpClient: HttpClient;
constructor(private endpoint: string) {
this.httpClient = new HttpClient();
}
constructor(public endpoint: string, public appService: AppService) {}
getAll = (headers?: HeadersInit) => {
return this.httpClient.get(this.endpoint, null, headers);
return this.appService.get(this.endpoint, null, headers);
};
get = (params?: Object, headers?: HeadersInit) => {
return this.httpClient.get(this.endpoint, params, headers);
return this.appService.get(this.endpoint, params, headers);
};
put = (data?: Object, headers?: HeadersInit) => {
return this.httpClient.put(this.endpoint, data, headers);
return this.appService.put(this.endpoint, data, headers);
};
post = (data?: Object, headers?: HeadersInit) => {
return this.httpClient.post(this.endpoint, data, headers);
return this.appService.post(this.endpoint, data, headers);
};
delete = (data?: Object, headers?: HeadersInit) => {
return this.httpClient.delete(this.endpoint, data, headers);
return this.appService.delete(this.endpoint, data, headers);
};
}

View File

@@ -77,7 +77,13 @@ export default HttpClient;
async function createRequest(request: Request): Promise<Response> {
let response: Response = await fetch(request);
if (!response.ok && response.status !== 403 && response.status !== 400) {
if (
!response.ok &&
response.status !== 403 &&
response.status !== 400 &&
response.status !== 401 &&
response.status !== 500
) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if (response.headers.get("Content-Type") !== null) {

View File

@@ -1,3 +1,4 @@
export { default as HttpClient } from "./http-service/HttpClient";
export { default as BaseService } from "./base-service/BaseService";
export { default as RouterService } from "./router-service/RouterService";
export { default as AppService } from "./app-service/AppService";

View File

@@ -17,12 +17,13 @@ class RouterService {
setRoutes = (routes: Array<any>) => {
if (!Array.isArray(this._routes)) this._routes = [];
routes.forEach((route) => {
const { path, component, data, layout } = route;
const { path, component, data, layout, middleware } = route;
const _routeState: RouteState = new RouteState(
path,
component,
data,
layout
layout,
middleware
);
this._routes?.push(_routeState);
});
@@ -30,10 +31,13 @@ class RouterService {
update() {
if (!this._routes) return;
const path = window.location.pathname;
const _mainRoot = this.mainRoot;
for (const route of this._routes) {
const path = window.location.pathname;
if (path == route.path) {
const _mainRoot = this.mainRoot;
if (route.middleware && typeof route.middleware == "function") {
if (route.middleware()) return;
}
let changed: boolean = false;
if (_mainRoot?.childNodes.length > 0) {
console.log(_mainRoot.childNodes);
@@ -94,6 +98,7 @@ class RouterService {
return;
}
}
_mainRoot.innerHTML = "404 - Not found";
}
@update
@@ -101,13 +106,27 @@ class RouterService {
if (!Array.isArray(this.historyStack)) this.historyStack = [];
const _index = this._routes.findIndex((route) => route.path === path);
if (_index >= 0) {
this.historyStack.push(this._routes[_index]);
const newRoute = this._routes[_index];
this.historyStack.push(newRoute);
const url = new URL(window.location.toString());
url.pathname = path;
window.history.pushState({}, "", url.toString());
}
}
@update
goBack() {
if (!Array.isArray(this.historyStack)) this.historyStack = [];
const lenHistory = this.historyStack.length;
if (lenHistory > 1) {
const nextRoute = this.historyStack[lenHistory - 2];
const url = new URL(window.location.toString());
url.pathname = nextRoute.path;
window.history.pushState({}, "", url.toString());
this.historyStack.pop();
}
}
@update
init() {}
}
@@ -117,7 +136,8 @@ class RouteState {
public path: string,
public component: string,
public data: any,
public layout: string
public layout: string,
public middleware: any
) {}
}

View File

@@ -0,0 +1,54 @@
import { AppService } from "core/services";
import { AuthService } from "services/";
class AuthStore {
private _token;
private _userDetails;
private authService: AuthService;
constructor(private appService: AppService) {
this.token = localStorage.getItem("token");
this.authService = new AuthService(this.appService);
}
get token() {
return this._token;
}
set token(token) {
this._token = token;
localStorage.setItem("token", token);
}
get user() {
return this._userDetails;
}
set user(userDetails) {
this._userDetails = userDetails;
}
userLogin = async (formObject) => {
try {
const response = await this.authService.login(formObject);
if (response?.token) {
this.token = response.token;
} else {
this.token = null;
localStorage.removeItem("token");
}
return response;
} catch (err) {
throw err;
}
};
userRegister = async (formObject) => {
try {
await this.authService.register(formObject);
} catch (err) {
throw err;
}
};
}
export default AuthStore;

1
src/core/store/index.ts Normal file
View File

@@ -0,0 +1 @@
export { default as AuthStore } from "./AuthStore";

View File

@@ -0,0 +1,4 @@
export default function firstUpper(s: string): string {
if (typeof s !== "string") return "";
return s.charAt(0).toUpperCase() + s.slice(1);
}

View File

@@ -3,3 +3,4 @@ export { default as update } from "./update-deco";
export { default as index } from "./index-deco";
export { default as closest } from "./closest-deco";
export { default as isTrue } from "./isTrue";
export { default as firstUpper } from "./first-upper";

View File

@@ -0,0 +1,3 @@
export default function randomId() {
return "_" + Math.random().toString(36).substr(2, 5);
}

View File

@@ -2,21 +2,25 @@ import { attr, targets, controller, target } from "@github/catalyst";
import { closest, index, update, isTrue } from "core/utils";
import { html, render, until } from "@github/jtml";
import { PingService } from "services/";
import { AppMainElement } from "components/";
@controller
class HomePageElement extends HTMLElement {
private pingService: PingService;
@closest appMain: AppMainElement;
constructor() {
super();
this.pingService = new PingService();
}
@update
connectedCallback() {}
connectedCallback() {
this.pingService = new PingService(this.appMain?.appService);
this.getPong();
}
getPong = async () => {
try {
const response = await this.pingService.getAll();
return response.api;
console.log(response);
} catch (err) {
console.log(err);
}

View File

@@ -1 +1,2 @@
export * from "./home-page/HomePageElement";
export * from "./register-page/RegisterPageElement";

View File

@@ -0,0 +1,85 @@
import { attr, targets, controller, target } from "@github/catalyst";
import { closest, index, update, isTrue } from "core/utils";
import { html, render, until } from "@github/jtml";
import { AuthService, PingService } from "services/";
import { AppMainElement, InputFieldElement } from "components/";
@controller
class RegisterPageElement extends HTMLElement {
@targets inputs: Array<InputFieldElement>;
@closest appMain: AppMainElement;
authService: AuthService;
constructor() {
super();
}
@update
connectedCallback() {
this.authService = new AuthService(this.appMain.appService);
}
get values(): Object {
const formObject = {};
this.inputs.forEach((input: InputFieldElement) => {
const inputType = input.inp;
formObject[input.name] = (inputType as HTMLInputElement).value;
});
return formObject;
}
onSubmit = async () => {
try {
if (!this.validate()) {
return;
}
const response = await this.appMain.authStore.userLogin(
this.values
);
console.log(response);
if (response?.token) {
this.appMain.routerService.goTo("/");
}
} catch (err) {
console.log(err);
}
};
validate(): boolean {
let _return = true;
this.inputs.forEach((input: InputFieldElement) => {
const valid: boolean = input.validate();
if (!valid) _return = false;
});
return _return;
}
update() {
render(
html`
<form>
<input-field
data-type="email"
data-name="email"
data-label="E-mail"
data-targets="register-page.inputs"
data-rules="required|isEmail"
></input-field>
<input-field
data-type="password"
data-name="password"
data-label="Password"
data-targets="register-page.inputs"
>
</input-field>
<button
type="button"
data-action="click:register-page#onSubmit"
>
Register
</button>
</form>
`,
this
);
}
}

View File

@@ -0,0 +1,15 @@
import { AppService, BaseService } from "core/services";
class PingService extends BaseService {
constructor(appService: AppService) {
super("/auth", appService);
}
login = (data?: Object, headers?: HeadersInit) => {
return this.appService.post(this.endpoint + "/login", data, headers);
};
register = (data?: Object, headers?: HeadersInit) => {
return this.appService.post(this.endpoint + "/register", data, headers);
};
}
export default PingService;

View File

@@ -1,8 +1,8 @@
import { BaseService } from "core/services";
import { AppService, BaseService } from "core/services";
class PingService extends BaseService {
constructor() {
super("/api");
constructor(appService: AppService) {
super("/wallet", appService);
}
}

View File

@@ -1 +1,2 @@
export { default as PingService } from "./PingService";
export { default as AuthService } from "./AuthService";