oauth workflow working

This commit is contained in:
Nicolas Constant 2018-09-09 01:29:23 -04:00
parent 89ee5fadf9
commit 0325b5bfbe
No known key found for this signature in database
GPG Key ID: 1E9F677FB01A5688
10 changed files with 141 additions and 172 deletions

13
main.js
View File

@ -8,15 +8,16 @@ const url = require('url')
function createWindow () {
// Create the browser window.
win = new BrowserWindow({ width: 395, height: 800, title: "Sengi", backgroundColor: '#FFF'})
win = new BrowserWindow({ width: 395, height: 800, title: "Sengi", backgroundColor: '#FFF'});
win.loadURL("http://localhost:4200");
win.setMenu(null);
// and load the index.html of the app.
win.loadURL(url.format({
pathname: path.join(__dirname, 'dist/index.html'),
protocol: 'file:',
slashes: true
}))
// win.loadURL(url.format({
// pathname: path.join(__dirname, 'dist/index.html'),
// protocol: 'file:',
// slashes: true
// }))
// Open the DevTools.
win.webContents.openDevTools()

View File

@ -22,6 +22,7 @@ import { AccountsService } from "./services/accounts.service";
import { StreamsService } from "./services/streams.service";
import { StreamingService } from "./services/streaming.service";
import { RegisteredAppsState } from "./states/registered-apps.state";
import { AccountsState } from "./states/accounts.state";
const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" },
@ -49,7 +50,8 @@ const routes: Routes = [
RouterModule.forRoot(routes),
NgxsModule.forRoot([
RegisteredAppsState
RegisteredAppsState,
AccountsState
]),
NgxsStoragePluginModule.forRoot()
],

View File

@ -1,8 +1,12 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subscription, BehaviorSubject } from "rxjs";
import { Subscription, BehaviorSubject, Observable } from "rxjs";
import { Store } from "@ngxs/store";
import { Account } from "../../services/models/mastodon.interfaces";
import { AccountWrapper } from "../../models/account.models";
import { AccountsService, LocalAccount } from "../../services/accounts.service";
import { AccountsService } from "../../services/accounts.service";
import { AccountsStateModel, AccountInfo } from "../../states/accounts.state";
@Component({
selector: "app-left-side-bar",
@ -11,35 +15,35 @@ import { AccountsService, LocalAccount } from "../../services/accounts.service";
})
export class LeftSideBarComponent implements OnInit, OnDestroy {
accounts: AccountWrapper[] = [];
accounts$: Observable<AccountInfo[]>;
private sub: Subscription;
constructor(
private readonly accountsService: AccountsService) { }
private readonly accountsService: AccountsService,
private readonly store: Store) {
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
ngOnInit() {
this.sub = this.accountsService.accountsSubject.subscribe((accounts: LocalAccount[]) => {
this.accounts.length = 0;
this.accounts$.subscribe((accounts: AccountInfo[]) => {
console.warn(accounts);
for (let acc of accounts) {
const accWrapper = new AccountWrapper();
accWrapper.username = `${acc.mastodonAccount.username}@${acc.mastodonInstance.replace("https://", "")}`;
accWrapper.avatar = acc.mastodonAccount.avatar;
this.accounts.push(accWrapper);
if (accounts) {
this.accounts.length = 0;
for (let acc of accounts) {
this.accountsService.retrieveAccountDetails(acc)
.then((result: Account) => {
const accWrapper = new AccountWrapper();
accWrapper.username = `${acc.username}@${acc.instance}`;
accWrapper.avatar = result.avatar;
this.accounts.push(accWrapper);
});
}
}
});
//const acc1 = new AccountWrapper();
//acc1.username = "@mastodon.social@Gargron";
//acc1.avatar = "https://files.mastodon.social/accounts/avatars/000/000/001/original/4df197532c6b768c.png";
//this.accounts.push(acc1);
//const acc2 = new AccountWrapper();
//acc2.username = "@mastodon.art@DearMsDearn";
//acc2.avatar = "https://curate.mastodon.art/gallery/accounts/avatars/000/015/092/original/3a112863f2dd22a27764179912dc8984.gif";
//this.accounts.push(acc2);
}
ngOnDestroy(): void {

View File

@ -2,7 +2,7 @@ import { Http, Headers, Response } from "@angular/http";
import { BehaviorSubject } from "rxjs";
import { AccountWrapper } from "./account.models";
import { LocalAccount } from "../services/accounts.service";
// import { LocalAccount } from "../services/accounts.service";
import { ApiRoutes } from "../services/models/api.settings";
import { Account, Status } from "../services/models/mastodon.interfaces";
import { StreamingService, StreamingWrapper } from "../services/streaming.service";
@ -15,8 +15,7 @@ export class Stream {
constructor(
private readonly httpService: Http,
public streamName: string,
private readonly type: StreamTypeEnum,
private readonly account: LocalAccount) {
private readonly type: StreamTypeEnum) {
this.retrieveToots(); //TODO change this for WebSockets
}
@ -31,17 +30,17 @@ export class Stream {
const route = this.getTimelineRoute();
const header = new Headers();
header.append("Authorization", `Bearer ${this.account.tokenData.access_token}`);
// header.append("Authorization", `Bearer ${this.account.tokenData.access_token}`);
this.httpService.get(this.account.mastodonInstance + route, { headers: header }).toPromise()
.then((res: Response) => {
const statuses = (res.json() as Status[])
.map((status: Status) => {
return new TootWrapper(status);
});
// this.httpService.get(this.account.mastodonInstance + route, { headers: header }).toPromise()
// .then((res: Response) => {
// const statuses = (res.json() as Status[])
// .map((status: Status) => {
// return new TootWrapper(status);
// });
this.statuses.next(statuses);
});
// this.statuses.next(statuses);
// });
}

View File

@ -7,6 +7,7 @@ import { AuthService } from "../../services/auth.service";
import { TokenData, AppData } from "../../services/models/mastodon.interfaces";
import { AccountsService } from "../../services/accounts.service";
import { AddRegisteredApp, RegisteredAppsState, RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
import { AccountInfo, AddAccount } from "../../states/accounts.state";
@Component({
selector: "app-register-new-account",
@ -40,15 +41,16 @@ export class RegisterNewAccountComponent implements OnInit {
console.warn(appInfo);
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
.then(tokenData => {
console.warn('Got token data!');
console.warn(tokenData);
localStorage.removeItem(this.authStorageKey);
//TODO review all this
// this.accountsService.addNewAccount(appDataWrapper.instance, appDataWrapper.username, tokenData);
.then((tokenData: TokenData) => {
const accountInfo = new AccountInfo();
accountInfo.username = appDataWrapper.username;
accountInfo.instance = appDataWrapper.instance;
accountInfo.token= tokenData;
this.store.dispatch([new AddAccount(accountInfo)])
.subscribe(() => {
localStorage.removeItem(this.authStorageKey);
});
});
});

View File

@ -4,97 +4,23 @@ import { Subject, BehaviorSubject } from "rxjs";
import { TokenData, Account } from "./models/mastodon.interfaces";
import { ApiRoutes } from "./models/api.settings";
import { AccountInfo } from "../states/accounts.state";
import { HttpClient, HttpHeaders } from "@angular/common/http";
@Injectable()
export class AccountsService {
private localAccountKey = "localAccounts";
private apiRoutes = new ApiRoutes();
accountsSubject: BehaviorSubject<LocalAccount[]>;
constructor(private readonly httpClient: HttpClient) {}
constructor(private readonly httpService: Http) {}
load(): Promise<boolean> {
return this.getAllLocalAccount()
.then((accounts) => {
this.accountsSubject = new BehaviorSubject<LocalAccount[]>(accounts);
return true;
});
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
const headers = new HttpHeaders({'Authorization':`Bearer ${account.token.access_token}`});
// const headers = new HttpHeaders({'Bearer':`${account.token}`});
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, {headers: headers}).toPromise();
}
addNewAccount(mastodonInstance: string, email: string, token: TokenData) {
const newAccount = new LocalAccount();
newAccount.mastodonInstance = mastodonInstance;
newAccount.email = email;
newAccount.tokenData = token;
this.getAllLocalAccount().then((allAccounts) => {
allAccounts.push(newAccount);
this.saveAccounts(allAccounts);
this.accountsSubject.next(allAccounts);
});
load(): any {
}
private getAllLocalAccount(): Promise<LocalAccount[]> {
const allSavedAccounts = this.retrieveSavedAccounts();
const allAccounts: LocalAccount[] = [];
const allTasks: Promise<any>[] = [];
for (let savedAcc of allSavedAccounts) {
const promise = this.retrieveMastodonDetails(savedAcc)
.then((acc) => {
allAccounts.push(acc);
})
.catch(err => console.error(err));
allTasks.push(promise);
}
return Promise.all(allTasks).then(() => {
return allAccounts;
});
}
private retrieveMastodonDetails(account: SavedLocalAccount): Promise<LocalAccount> {
const localAccount = new LocalAccount();
localAccount.mastodonInstance = account.mastodonInstance;
localAccount.email = account.email;
localAccount.tokenData = account.tokenData;
const header = new Headers();
header.append("Authorization", `Bearer ${localAccount.tokenData.access_token}`);
return this.httpService.get('https://' + localAccount.mastodonInstance + this.apiRoutes.getCurrentAccount, { headers: header }).toPromise()
.then((res: Response) => {
const mastodonAccount = res.json() as Account;
localAccount.mastodonAccount = mastodonAccount;
return localAccount;
});
}
private retrieveSavedAccounts(): SavedLocalAccount[] {
const savedData = <SavedLocalAccount[]>JSON.parse(localStorage.getItem(this.localAccountKey));
if (savedData) {
return savedData;
}
return [];
}
private saveAccounts(accounts: SavedLocalAccount[]) {
localStorage.setItem(this.localAccountKey, JSON.stringify(accounts));
}
}
class SavedLocalAccount {
mastodonInstance: string;
email: string;
tokenData: TokenData;
}
export class LocalAccount implements SavedLocalAccount {
mastodonAccount: Account;
mastodonInstance: string;
email: string;
tokenData: TokenData;
}
}

View File

@ -5,27 +5,27 @@ import { HttpClient } from "@angular/common/http";
@Injectable()
export class AuthService {
private apiRoutes = new ApiRoutes();
private apiRoutes = new ApiRoutes();
constructor(
private readonly httpClient: HttpClient) {
}
constructor(
private readonly httpClient: HttpClient) {
}
getToken(instance: string, client_id: string, client_secret: string, code: string, redirect_uri: string): Promise<TokenData> {
const url = `https://${instance}/oauth/token?client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirect_uri)}`;
getToken(instance: string, client_id: string, client_secret: string, code: string, redirect_uri: string): Promise<TokenData> {
const url = `https://${instance}/oauth/token?client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirect_uri)}`;
return this.httpClient.post<TokenData>(url, null).toPromise();
}
return this.httpClient.post<TokenData>(url, null).toPromise();
}
createNewApplication(instance: string, appName: string, redirectUrl: string, scopes: string, website: string): Promise<AppData> {
const url = 'https://' + instance + this.apiRoutes.createApp;
const formData = new FormData();
createNewApplication(instance: string, appName: string, redirectUrl: string, scopes: string, website: string): Promise<AppData> {
const url = 'https://' + instance + this.apiRoutes.createApp;
const formData = new FormData();
formData.append('client_name', appName);
formData.append('redirect_uris', redirectUrl);
formData.append('scopes', scopes);
formData.append('website', website);
formData.append('client_name', appName);
formData.append('redirect_uris', redirectUrl);
formData.append('scopes', scopes);
formData.append('website', website);
return this.httpClient.post<AppData>(url, formData).toPromise();
}
return this.httpClient.post<AppData>(url, formData).toPromise();
}
}

View File

@ -3,7 +3,7 @@ import { Http } from "@angular/http";
import { BehaviorSubject } from "rxjs";
import { Stream, StreamTypeEnum } from "../models/stream.models";
import { AccountsService, LocalAccount } from "./accounts.service";
import { AccountsService } from "./accounts.service";
@Injectable()
export class StreamsService {
@ -11,23 +11,24 @@ export class StreamsService {
constructor(
private readonly httpService: Http,
private readonly accountsService: AccountsService) {
// private readonly accountsService: AccountsService
) {
// Return home/local/public of all accounts
this.accountsService.accountsSubject
.subscribe((accounts: LocalAccount[]) => {
const streams: Stream[] = [];
for (let acc of accounts) {
const homeStream = new Stream(this.httpService, "Home", StreamTypeEnum.Home, acc);
const localStream = new Stream(this.httpService, "Local", StreamTypeEnum.Local, acc);
const publicStream = new Stream(this.httpService, "Public", StreamTypeEnum.Public, acc);
// this.accountsService.accountsSubject
// .subscribe((accounts: LocalAccount[]) => {
// const streams: Stream[] = [];
// for (let acc of accounts) {
// const homeStream = new Stream(this.httpService, "Home", StreamTypeEnum.Home, acc);
// const localStream = new Stream(this.httpService, "Local", StreamTypeEnum.Local, acc);
// const publicStream = new Stream(this.httpService, "Public", StreamTypeEnum.Public, acc);
streams.push(homeStream);
streams.push(localStream);
streams.push(publicStream);
}
this.streamsSubject.next(streams);
});
// streams.push(homeStream);
// streams.push(localStream);
// streams.push(publicStream);
// }
// this.streamsSubject.next(streams);
// });
}

View File

@ -0,0 +1,34 @@
import { State, Action, StateContext } from '@ngxs/store';
import { TokenData } from '../services/models/mastodon.interfaces';
export class AddAccount {
static readonly type = '[Accounts] Add account';
constructor(public account: AccountInfo) {}
}
export interface AccountsStateModel {
accounts: AccountInfo[];
}
@State<AccountsStateModel>({
name: 'registeredaccounts',
defaults: {
accounts: []
}
})
export class AccountsState {
@Action(AddAccount)
AddRegisteredApp(ctx: StateContext<AccountsStateModel>, action: AddAccount) {
const state = ctx.getState();
ctx.patchState({
accounts: [...state.accounts, action.account]
});
}
}
export class AccountInfo {
username: string;
instance: string;
token: TokenData;
}

View File

@ -1,6 +1,6 @@
import { State, Action, StateContext } from '@ngxs/store';
import { AppData } from '../services/models/mastodon.interfaces';
export class AddRegisteredApp {
static readonly type = '[RegisteredApps] Add app';
constructor(public app: AppInfo) { }
@ -11,12 +11,12 @@ export interface RegisteredAppsStateModel {
}
@State<RegisteredAppsStateModel>({
name: 'registeredapps',
defaults: {
apps: []
}
name: 'registeredapps',
defaults: {
apps: []
}
})
export class RegisteredAppsState {
export class RegisteredAppsState {
@Action(AddRegisteredApp)
AddRegisteredApp(ctx: StateContext<RegisteredAppsStateModel>, action: AddRegisteredApp) {
const state = ctx.getState();