Merge pull request #236 from NicolasConstant/topic_simplify-add-account

Topic simplify add account
This commit is contained in:
Nicolas Constant 2020-03-07 19:56:34 -05:00 committed by GitHub
commit c27ed4dc2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 131 deletions

View File

@ -21,9 +21,9 @@
"test-nowatch": "ng test --watch=false", "test-nowatch": "ng test --watch=false",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"electron": "ng build --prod && electron .", "electron": "electron .",
"electron-prod": "ng build --prod && electron .",
"electron-debug": "ng build && electron .", "electron-debug": "ng build && electron .",
"electron-test": "electron .",
"dist": "npm run build && electron-builder --publish onTagOrDraft", "dist": "npm run build && electron-builder --publish onTagOrDraft",
"travis": "electron-builder --publish onTagOrDraft" "travis": "electron-builder --publish onTagOrDraft"
}, },

View File

@ -1,16 +1,25 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription, Observable, Subject } from 'rxjs'; import { Subscription, Observable, Subject } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators'; import { debounceTime, map } from 'rxjs/operators';
import { Select } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
import { faTimes } from "@fortawesome/free-solid-svg-icons"; import { faTimes } from "@fortawesome/free-solid-svg-icons";
// import { ElectronService } from 'ngx-electron';
import { NavigationService, LeftPanelType, OpenLeftPanelEvent } from './services/navigation.service'; import { NavigationService, LeftPanelType, OpenLeftPanelEvent } from './services/navigation.service';
import { StreamElement } from './states/streams.state'; import { StreamElement } from './states/streams.state';
import { AccountInfo, AddAccount } from "./states/accounts.state";
import { OpenMediaEvent } from './models/common.model'; import { OpenMediaEvent } from './models/common.model';
import { ToolsService } from './services/tools.service'; import { ToolsService } from './services/tools.service';
import { MediaService } from './services/media.service'; import { MediaService } from './services/media.service';
import { ServiceWorkerService } from './services/service-worker.service'; import { ServiceWorkerService } from './services/service-worker.service';
import { AuthService, CurrentAuthProcess } from './services/auth.service';
import { MastodonWrapperService } from './services/mastodon-wrapper.service';
import { TokenData, Account } from './services/models/mastodon.interfaces';
import { NotificationService } from './services/notification.service';
import { AppInfo, RegisteredAppsStateModel } from './states/registered-apps.state';
import { HttpErrorResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -25,15 +34,24 @@ export class AppComponent implements OnInit, OnDestroy {
openedMediaEvent: OpenMediaEvent openedMediaEvent: OpenMediaEvent
updateAvailable: boolean; updateAvailable: boolean;
private authStorageKey: string = 'tempAuth';
private columnEditorSub: Subscription; private columnEditorSub: Subscription;
private openMediaSub: Subscription; private openMediaSub: Subscription;
private streamSub: Subscription; private streamSub: Subscription;
private dragoverSub: Subscription; private dragoverSub: Subscription;
private updateAvailableSub: Subscription; private updateAvailableSub: Subscription;
private paramsSub: Subscription;
@Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>; @Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>;
constructor( constructor(
private readonly router: Router,
private readonly notificationService: NotificationService,
private readonly store: Store,
private readonly mastodonService: MastodonWrapperService,
private readonly authService: AuthService,
private readonly activatedRoute: ActivatedRoute,
private readonly serviceWorkerService: ServiceWorkerService, private readonly serviceWorkerService: ServiceWorkerService,
private readonly toolsService: ToolsService, private readonly toolsService: ToolsService,
private readonly mediaService: MediaService, private readonly mediaService: MediaService,
@ -41,6 +59,60 @@ export class AppComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.paramsSub = this.activatedRoute.queryParams.subscribe(params => {
const code = params['code'];
if (!code) {
return;
}
const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey));
if (!appDataWrapper) {
this.notificationService.notify('', 400, 'Something when wrong in the authentication process. Please retry.', true);
this.router.navigate(['/']);
return;
}
const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
let usedTokenData: TokenData;
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
.then((tokenData: TokenData) => {
if(tokenData.refresh_token && !tokenData.created_at){
const nowEpoch = Date.now() / 1000 | 0;
tokenData.created_at = nowEpoch;
}
usedTokenData = tokenData;
return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData });
})
.then((account: Account) => {
var username = account.username.toLowerCase();
var instance = appDataWrapper.instance.toLowerCase();
if(this.isAccountAlreadyPresent(username, instance)){
this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true);
this.router.navigate(['/']);
return;
}
const accountInfo = new AccountInfo();
accountInfo.username = username;
accountInfo.instance = instance;
accountInfo.token = usedTokenData;
this.store.dispatch([new AddAccount(accountInfo)])
.subscribe(() => {
localStorage.removeItem(this.authStorageKey);
this.router.navigate(['/']);
});
})
.catch((err: HttpErrorResponse) => {
this.notificationService.notifyHttpError(err, null);
this.router.navigate(['/']);
});
});
this.updateAvailableSub = this.serviceWorkerService.newAppVersionIsAvailable.subscribe((updateAvailable) => { this.updateAvailableSub = this.serviceWorkerService.newAppVersionIsAvailable.subscribe((updateAvailable) => {
this.updateAvailable = updateAvailable; this.updateAvailable = updateAvailable;
}); });
@ -69,7 +141,6 @@ export class AppComponent implements OnInit, OnDestroy {
} }
}); });
this.dragoverSub = this.dragoverSubject this.dragoverSub = this.dragoverSubject
.pipe( .pipe(
debounceTime(1500) debounceTime(1500)
@ -85,6 +156,7 @@ export class AppComponent implements OnInit, OnDestroy {
this.openMediaSub.unsubscribe(); this.openMediaSub.unsubscribe();
this.dragoverSub.unsubscribe(); this.dragoverSub.unsubscribe();
this.updateAvailableSub.unsubscribe(); this.updateAvailableSub.unsubscribe();
this.paramsSub.unsubscribe();
} }
closeMedia() { closeMedia() {
@ -131,4 +203,19 @@ export class AppComponent implements OnInit, OnDestroy {
this.updateAvailable = false; this.updateAvailable = false;
return false; return false;
} }
private isAccountAlreadyPresent(username: string, instance: string): boolean{
const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
for (let acc of accounts) {
if(acc.instance === instance && acc.username == username){
return true;
}
}
return false;
}
private getAllSavedApps(): AppInfo[] {
const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps;
return snapshot.apps;
}
} }

View File

@ -23,7 +23,7 @@ import { LeftSideBarComponent } from "./components/left-side-bar/left-side-bar.c
import { StreamsMainDisplayComponent } from "./pages/streams-main-display/streams-main-display.component"; import { StreamsMainDisplayComponent } from "./pages/streams-main-display/streams-main-display.component";
import { StreamComponent } from "./components/stream/stream.component"; import { StreamComponent } from "./components/stream/stream.component";
import { StreamsSelectionFooterComponent } from "./components/streams-selection-footer/streams-selection-footer.component"; import { StreamsSelectionFooterComponent } from "./components/streams-selection-footer/streams-selection-footer.component";
import { RegisterNewAccountComponent } from "./pages/register-new-account/register-new-account.component"; // import { RegisterNewAccountComponent } from "./pages/register-new-account/register-new-account.component";
import { AuthService } from "./services/auth.service"; import { AuthService } from "./services/auth.service";
import { StreamingService } from "./services/streaming.service"; import { StreamingService } from "./services/streaming.service";
import { RegisteredAppsState } from "./states/registered-apps.state"; import { RegisteredAppsState } from "./states/registered-apps.state";
@ -84,10 +84,10 @@ import { environment } from '../environments/environment';
const routes: Routes = [ const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" }, { path: "", component: StreamsMainDisplayComponent },
{ path: "home", component: StreamsMainDisplayComponent }, // { path: "home", component: StreamsMainDisplayComponent },
{ path: "register", component: RegisterNewAccountComponent }, // { path: "register", component: RegisterNewAccountComponent },
{ path: "**", redirectTo: "home" } { path: "**", redirectTo: "" }
]; ];
@NgModule({ @NgModule({
@ -98,7 +98,7 @@ const routes: Routes = [
StreamComponent, StreamComponent,
StreamsSelectionFooterComponent, StreamsSelectionFooterComponent,
StatusComponent, StatusComponent,
RegisterNewAccountComponent, // RegisterNewAccountComponent,
AccountIconComponent, AccountIconComponent,
FloatingColumnComponent, FloatingColumnComponent,
ManageAccountComponent, ManageAccountComponent,

View File

@ -1,124 +1,124 @@
import { Component, OnInit, Input } from "@angular/core"; // import { Component, OnInit, Input } from "@angular/core";
import { Store, Select } from '@ngxs/store'; // import { Store, Select } from '@ngxs/store';
import { ActivatedRoute, Router } from "@angular/router"; // import { ActivatedRoute, Router } from "@angular/router";
import { HttpErrorResponse } from "@angular/common/http"; // import { HttpErrorResponse } from "@angular/common/http";
import { AuthService, CurrentAuthProcess } from "../../services/auth.service"; // import { AuthService, CurrentAuthProcess } from "../../services/auth.service";
import { TokenData, Account } from "../../services/models/mastodon.interfaces"; // import { TokenData, Account } from "../../services/models/mastodon.interfaces";
import { RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state"; // import { RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
import { AccountInfo, AddAccount, AccountsStateModel } from "../../states/accounts.state"; // import { AccountInfo, AddAccount, AccountsStateModel } from "../../states/accounts.state";
import { NotificationService } from "../../services/notification.service"; // import { NotificationService } from "../../services/notification.service";
import { MastodonWrapperService } from '../../services/mastodon-wrapper.service'; // import { MastodonWrapperService } from '../../services/mastodon-wrapper.service';
@Component({ // @Component({
selector: "app-register-new-account", // selector: "app-register-new-account",
templateUrl: "./register-new-account.component.html", // templateUrl: "./register-new-account.component.html",
styleUrls: ["./register-new-account.component.scss"] // styleUrls: ["./register-new-account.component.scss"]
}) // })
export class RegisterNewAccountComponent implements OnInit { // export class RegisterNewAccountComponent implements OnInit {
@Input() mastodonFullHandle: string; // // @Input() mastodonFullHandle: string;
hasError: boolean; // hasError: boolean;
errorMessage: string; // errorMessage: string;
private authStorageKey: string = 'tempAuth'; // private authStorageKey: string = 'tempAuth';
constructor( // constructor(
private readonly mastodonService: MastodonWrapperService, // private readonly mastodonService: MastodonWrapperService,
private readonly notificationService: NotificationService, // private readonly notificationService: NotificationService,
private readonly authService: AuthService, // private readonly authService: AuthService,
private readonly store: Store, // private readonly store: Store,
private readonly activatedRoute: ActivatedRoute, // private readonly activatedRoute: ActivatedRoute,
private readonly router: Router) { // private readonly router: Router) {
this.activatedRoute.queryParams.subscribe(params => { // this.activatedRoute.queryParams.subscribe(params => {
this.hasError = false; // this.hasError = false;
const code = params['code']; // const code = params['code'];
if (!code) { // if (!code) {
this.displayError(RegistrationErrorTypes.CodeNotFound); // this.displayError(RegistrationErrorTypes.CodeNotFound);
return; // return;
} // }
const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey)); // const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey));
if (!appDataWrapper) { // if (!appDataWrapper) {
this.displayError(RegistrationErrorTypes.AuthProcessNotFound); // this.displayError(RegistrationErrorTypes.AuthProcessNotFound);
return; // return;
} // }
const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0]; // const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
let usedTokenData: TokenData; // let usedTokenData: TokenData;
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri) // this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
.then((tokenData: TokenData) => { // .then((tokenData: TokenData) => {
if(tokenData.refresh_token && !tokenData.created_at){ // if(tokenData.refresh_token && !tokenData.created_at){
const nowEpoch = Date.now() / 1000 | 0; // const nowEpoch = Date.now() / 1000 | 0;
tokenData.created_at = nowEpoch; // tokenData.created_at = nowEpoch;
} // }
usedTokenData = tokenData; // usedTokenData = tokenData;
return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData }); // return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData });
}) // })
.then((account: Account) => { // .then((account: Account) => {
var username = account.username.toLowerCase(); // var username = account.username.toLowerCase();
var instance = appDataWrapper.instance.toLowerCase(); // var instance = appDataWrapper.instance.toLowerCase();
if(this.isAccountAlreadyPresent(username, instance)){ // if(this.isAccountAlreadyPresent(username, instance)){
this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true); // this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true);
this.router.navigate(['/home']); // this.router.navigate(['/home']);
return; // return;
} // }
const accountInfo = new AccountInfo(); // const accountInfo = new AccountInfo();
accountInfo.username = username; // accountInfo.username = username;
accountInfo.instance = instance; // accountInfo.instance = instance;
accountInfo.token = usedTokenData; // accountInfo.token = usedTokenData;
this.store.dispatch([new AddAccount(accountInfo)]) // this.store.dispatch([new AddAccount(accountInfo)])
.subscribe(() => { // .subscribe(() => {
localStorage.removeItem(this.authStorageKey); // localStorage.removeItem(this.authStorageKey);
this.router.navigate(['/home']); // this.router.navigate(['/home']);
}); // });
}) // })
.catch((err: HttpErrorResponse) => { // .catch((err: HttpErrorResponse) => {
this.notificationService.notifyHttpError(err, null); // this.notificationService.notifyHttpError(err, null);
}); // });
}); // });
} // }
ngOnInit() { // ngOnInit() {
} // }
private isAccountAlreadyPresent(username: string, instance: string): boolean{ // private isAccountAlreadyPresent(username: string, instance: string): boolean{
const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts; // const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
for (let acc of accounts) { // for (let acc of accounts) {
if(acc.instance === instance && acc.username == username){ // if(acc.instance === instance && acc.username == username){
return true; // return true;
} // }
} // }
return false; // return false;
} // }
private displayError(type: RegistrationErrorTypes) { // private displayError(type: RegistrationErrorTypes) {
this.hasError = true; // this.hasError = true;
switch (type) { // switch (type) {
case RegistrationErrorTypes.AuthProcessNotFound: // case RegistrationErrorTypes.AuthProcessNotFound:
this.errorMessage = 'Something when wrong in the authentication process. Please retry.' // this.errorMessage = 'Something when wrong in the authentication process. Please retry.'
break; // break;
case RegistrationErrorTypes.CodeNotFound: // case RegistrationErrorTypes.CodeNotFound:
this.errorMessage = 'No authentication code returned. Please retry.' // this.errorMessage = 'No authentication code returned. Please retry.'
break; // break;
} // }
} // }
private getAllSavedApps(): AppInfo[] { // private getAllSavedApps(): AppInfo[] {
const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps; // const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps;
return snapshot.apps; // return snapshot.apps;
} // }
} // }
enum RegistrationErrorTypes { // enum RegistrationErrorTypes {
CodeNotFound, // CodeNotFound,
AuthProcessNotFound // AuthProcessNotFound
} // }

View File

@ -1,5 +1,4 @@
import { Component, OnInit, OnDestroy, QueryList, ViewChildren, ElementRef } from "@angular/core"; import { Component, OnInit, OnDestroy, QueryList, ViewChildren, ElementRef } from "@angular/core";
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription } from "rxjs"; import { Observable, Subscription } from "rxjs";
import { Select } from "@ngxs/store"; import { Select } from "@ngxs/store";
import scrollIntoView from "smooth-scroll-into-view-if-needed"; import scrollIntoView from "smooth-scroll-into-view-if-needed";
@ -20,20 +19,10 @@ export class StreamsMainDisplayComponent implements OnInit, OnDestroy {
private columnSelectedSub: Subscription; private columnSelectedSub: Subscription;
constructor( constructor(
private readonly router: Router,
private readonly activatedRoute: ActivatedRoute,
private readonly navigationService: NavigationService) { private readonly navigationService: NavigationService) {
} }
ngOnInit() { ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => {
const code = params['code'];
if (code) {
this.router.navigate(['/register'], { queryParams: { code: code} });
return;
}
});
this.columnSelectedSub = this.navigationService.columnSelectedSubject.subscribe((columnIndex: number) => { this.columnSelectedSub = this.navigationService.columnSelectedSubject.subscribe((columnIndex: number) => {
this.focusOnColumn(columnIndex); this.focusOnColumn(columnIndex);
}); });
@ -55,10 +44,6 @@ export class StreamsMainDisplayComponent implements OnInit, OnDestroy {
.then(() => { .then(() => {
this.streamComponents.toArray()[columnIndex].focus(); this.streamComponents.toArray()[columnIndex].focus();
}); });
// setTimeout(() => {
// this.streamComponents.toArray()[columnIndex].focus();
// }, 500);
}, 0); }, 0);

View File

@ -22,6 +22,9 @@ export class MastodonWrapperService {
let isExpired = false; let isExpired = false;
let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id); let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id);
if(!storedAccountInfo || !(storedAccountInfo.token))
return Promise.resolve(accountInfo);
try { try {
if (storedAccountInfo.token.refresh_token) { if (storedAccountInfo.token.refresh_token) {
if (!storedAccountInfo.token.created_at || !storedAccountInfo.token.expires_in) { if (!storedAccountInfo.token.created_at || !storedAccountInfo.token.expires_in) {