...
 
Commits (2)
<ng-container *ngIf="!isProDomain">
<m-v2-topbar *mIfFeature="'top-feeds'; else legacyTopbar">
<ng-container search>
<m-search--bar [defaultSizes]="false"></m-search--bar>
</ng-container>
<ng-container icons>
<m-notifications--topbar-toggle
*ngIf="session.isLoggedIn()"
></m-notifications--topbar-toggle>
</ng-container>
</m-v2-topbar>
<ng-template #legacyTopbar>
<m-topbar class="m-noshadow">
<ng-container *ngIf="ready">
<ng-container *ngIf="!isProDomain">
<m-v2-topbar *mIfFeature="'top-feeds'; else legacyTopbar">
<ng-container search>
<m-search--bar></m-search--bar>
<m-search--bar [defaultSizes]="false"></m-search--bar>
</ng-container>
<ng-container icons>
<m-notifications--topbar-toggle></m-notifications--topbar-toggle>
<m-wallet--topbar-toggle></m-wallet--topbar-toggle>
<m-notifications--topbar-toggle
*ngIf="session.isLoggedIn()"
></m-notifications--topbar-toggle>
</ng-container>
</m-topbar>
</ng-template>
</m-v2-topbar>
<ng-template #legacyTopbar>
<m-topbar class="m-noshadow">
<ng-container search>
<m-search--bar></m-search--bar>
</ng-container>
<m-sidebar--markers
<ng-container icons>
<m-notifications--topbar-toggle></m-notifications--topbar-toggle>
<m-wallet--topbar-toggle></m-wallet--topbar-toggle>
</ng-container>
</m-topbar>
</ng-template>
<m-sidebar--markers
[class.has-v2-navbar]="featuresService.has('top-feeds')"
></m-sidebar--markers>
</ng-container>
<m-body
[class.has-v2-navbar]="featuresService.has('top-feeds')"
></m-sidebar--markers>
</ng-container>
[class.is-pro-domain]="isProDomain"
>
<m-announcement [id]="'blockchain:sale'" *ngIf="false">
<span
class="m-blockchain--wallet-address-notice--action"
routerLink="/tokens"
i18n="@@BLOCKCHAIN__SALE__NOTICE"
>
The MINDS token is now live. Learn more here.
</span>
</m-announcement>
<m-blockchain--wallet-address-notice></m-blockchain--wallet-address-notice>
<router-outlet></router-outlet>
</m-body>
<m-messenger *ngIf="minds.LoggedIn && !isProDomain"></m-messenger>
<m-hovercard-popup></m-hovercard-popup>
<m-body
[class.has-v2-navbar]="featuresService.has('top-feeds')"
[class.is-pro-domain]="isProDomain"
>
<m-announcement [id]="'blockchain:sale'" *ngIf="false">
<span
class="m-blockchain--wallet-address-notice--action"
routerLink="/tokens"
i18n="@@BLOCKCHAIN__SALE__NOTICE"
>
The MINDS token is now live. Learn more here.
</span>
</m-announcement>
<m-blockchain--wallet-address-notice></m-blockchain--wallet-address-notice>
<router-outlet></router-outlet>
</m-body>
<m-messenger *ngIf="minds.LoggedIn && !isProDomain"></m-messenger>
<m-hovercard-popup></m-hovercard-popup>
<m-overlay-modal></m-overlay-modal>
<m--blockchain--transaction-overlay></m--blockchain--transaction-overlay>
<m-modal--tos-updated *ngIf="session.isLoggedIn()"></m-modal--tos-updated>
<m-juryDutySession__summons
*ngIf="session.isLoggedIn() && !isProDomain"
></m-juryDutySession__summons>
<m-modal-signup-on-scroll *ngIf="!isProDomain"></m-modal-signup-on-scroll>
<m-channel--onboarding *ngIf="showOnboarding"></m-channel--onboarding>
<m-cookies-notice *ngIf="!session.isLoggedIn()"> </m-cookies-notice>
<m-overlay-modal></m-overlay-modal>
<m--blockchain--transaction-overlay></m--blockchain--transaction-overlay>
<m-modal--tos-updated *ngIf="session.isLoggedIn()"></m-modal--tos-updated>
<m-juryDutySession__summons
*ngIf="session.isLoggedIn() && !isProDomain"
></m-juryDutySession__summons>
<m-modal-signup-on-scroll *ngIf="!isProDomain"></m-modal-signup-on-scroll>
<m-channel--onboarding *ngIf="showOnboarding"></m-channel--onboarding>
<m-cookies-notice *ngIf="!session.isLoggedIn()"> </m-cookies-notice>
</ng-container>
import { Component, HostBinding } from '@angular/core';
import { ChangeDetectorRef, Component, HostBinding } from '@angular/core';
import { NotificationService } from './modules/notifications/notification.service';
import { AnalyticsService } from './services/analytics';
......@@ -18,6 +18,7 @@ import { ThemeService } from './common/services/theme.service';
import { BannedService } from './modules/report/banned/banned.service';
import { DiagnosticsService } from './services/diagnostics.service';
import { SiteService } from './common/services/site.service';
import { SsoService } from './common/services/sso.service';
import { Subscription } from 'rxjs';
import { RouterHistoryService } from './common/services/router-history.service';
import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module';
......@@ -29,8 +30,11 @@ import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module';
})
export class Minds {
name: string;
minds = window.Minds;
ready: boolean = false;
showOnboarding: boolean = false;
showTOSModal: boolean = false;
......@@ -57,7 +61,9 @@ export class Minds {
private bannedService: BannedService,
private diagnostics: DiagnosticsService,
private routerHistoryService: RouterHistoryService,
private site: SiteService
private site: SiteService,
private sso: SsoService,
private cd: ChangeDetectorRef
) {
this.name = 'Minds';
......@@ -67,8 +73,29 @@ export class Minds {
}
async ngOnInit() {
this.diagnostics.setUser(this.minds.user);
this.diagnostics.listen(); // Listen for user changes
try {
this.diagnostics.setUser(this.minds.user);
this.diagnostics.listen(); // Listen for user changes
if (this.sso.isRequired()) {
await this.sso.connect();
}
} catch (e) {
console.error('ngOnInit()', e);
}
this.ready = true;
this.detectChanges();
try {
await this.initialize();
} catch (e) {
console.error('initialize()', e);
}
}
async initialize() {
this.blockListService.fetch();
if (!this.site.isProDomain) {
this.notificationService.getNotifications();
......@@ -136,4 +163,9 @@ export class Minds {
get isProDomain() {
return this.site.isProDomain;
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
import { Cookie } from '../../services/cookie';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { Location } from '@angular/common';
import { SiteService } from '../services/site.service';
/**
* API Class
*/
export class MindsHttpClient {
base: string = '/';
origin: string = '';
cookie: Cookie = new Cookie();
static _(http: HttpClient, site: SiteService) {
return new MindsHttpClient(http, site);
static _(http: HttpClient) {
return new MindsHttpClient(http);
}
constructor(public http: HttpClient, protected site: SiteService) {
if (this.site.isProDomain) {
this.base = window.Minds.site_url;
this.origin = document.location.host;
}
}
constructor(public http: HttpClient) {}
/**
* Return a GET request
......@@ -81,22 +73,11 @@ export class MindsHttpClient {
'X-VERSION': environment.version,
};
if (this.origin) {
const PRO_XSRF_JWT = this.cookie.get('PRO-XSRF-JWT') || '';
headers['X-MINDS-ORIGIN'] = this.origin;
headers['X-PRO-XSRF-JWT'] = PRO_XSRF_JWT;
}
const builtOptions = {
headers: new HttpHeaders(headers),
cache: true,
};
if (this.origin) {
builtOptions['withCredentials'] = true;
}
return Object.assign(options, builtOptions);
}
}
......
......@@ -121,9 +121,10 @@ import { PageLayoutComponent } from './components/page-layout/page-layout.compon
import { DashboardLayoutComponent } from './components/dashboard-layout/dashboard-layout.component';
import { ShadowboxLayoutComponent } from './components/shadowbox-layout/shadowbox-layout.component';
import { ShadowboxHeaderComponent } from './components/shadowbox-header/shadowbox-header.component';
import { ShadowboxSubmitButtonComponent } from './components/shadowbox-submit-button/shadowbox-submit-button.component';
import { FormDescriptorComponent } from './components/form-descriptor/form-descriptor.component';
import { FormToastComponent } from './components/form-toast/form-toast.component';
import { ShadowboxSubmitButtonComponent } from './components/shadowbox-submit-button/shadowbox-submit-button.component';
import { SsoService } from './services/sso.service';
PlotlyModule.plotlyjs = PlotlyJS;
......@@ -339,6 +340,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
],
providers: [
SiteService,
SsoService,
{
provide: AttachmentService,
useFactory: AttachmentService._,
......@@ -354,7 +356,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
{
provide: MindsHttpClient,
useFactory: MindsHttpClient._,
deps: [HttpClient, SiteService],
deps: [HttpClient],
},
{
provide: NSFWSelectorCreatorService,
......
......@@ -14,7 +14,6 @@ export class BlockListService {
protected storage: Storage
) {
this.blocked = new BehaviorSubject(JSON.parse(this.storage.get('blocked')));
this.fetch();
}
fetch() {
......
import { Injectable } from '@angular/core';
import { SiteService } from './site.service';
import { Client } from '../../services/api/client';
import { Session } from '../../services/session';
@Injectable()
export class SsoService {
protected readonly minds = window.Minds;
constructor(
protected site: SiteService,
protected client: Client,
protected session: Session
) {
this.listen();
}
listen() {
this.session.isLoggedIn((is: boolean) => {
if (is) {
this.auth();
}
});
}
isRequired(): boolean {
return this.site.isProDomain;
}
async connect() {
try {
const connect: any = await this.client.postRaw(
`${this.minds.site_url}api/v2/sso/connect`
);
if (connect && connect.token && connect.status === 'success') {
const authorization: any = await this.client.post(
'api/v2/sso/authorize',
{
token: connect.token,
}
);
if (authorization && authorization.user) {
this.session.inject(authorization.user);
}
}
} catch (e) {
console.error(e);
}
}
async auth() {
try {
const connect: any = await this.client.post('api/v2/sso/connect');
if (connect && connect.token && connect.status === 'success') {
await this.client.postRaw(
`${this.minds.site_url}api/v2/sso/authorize`,
{
token: connect.token,
}
);
}
} catch (e) {
console.error(e);
}
}
}
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { Subscription } from 'rxjs';
......@@ -23,6 +23,8 @@ export class SignupOnScrollModal implements OnInit, OnDestroy {
routerSubscription: Subscription;
@Input() disableScrollListener: true;
@ViewChild('modal', { static: true }) modal: SignupModal;
constructor(
......@@ -41,6 +43,10 @@ export class SignupOnScrollModal implements OnInit, OnDestroy {
}
listen() {
if (!this.disableScrollListener) {
return;
}
this.routerSubscription = this.router.events.subscribe(
(navigationEvent: NavigationEnd) => {
try {
......@@ -90,7 +96,10 @@ export class SignupOnScrollModal implements OnInit, OnDestroy {
}
unListen() {
this.routerSubscription.unsubscribe();
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
this.unlistenScroll();
}
......
......@@ -124,4 +124,6 @@
</div>
<m-overlay-modal #overlayModal></m-overlay-modal>
<m-modal-signup-on-scroll></m-modal-signup-on-scroll>
<m-modal-signup-on-scroll
[disableScrollListener]="false"
></m-modal-signup-on-scroll>
......@@ -229,7 +229,7 @@ export class ProChannelComponent implements OnInit, AfterViewInit, OnDestroy {
this.detectChanges();
try {
this.channel = await this.channelService.loadAndAuth(this.username);
this.channel = await this.channelService.load(this.username);
this.bindCssVariables();
this.shouldOpenWireModal();
......
......@@ -74,21 +74,16 @@ export class ProChannelService implements OnDestroy {
this.isLoggedIn$.unsubscribe();
}
async loadAndAuth(id: string): Promise<MindsUser> {
async load(id: string): Promise<MindsUser> {
try {
this.currentChannel = void 0;
const response = (await this.client.get(`api/v2/pro/channel/${id}`)) as {
channel;
me?;
};
this.currentChannel = response.channel;
if (this.site.isProDomain && response.me) {
this.session.login(response.me);
}
if (!this.currentChannel.pro_settings.tag_list) {
this.currentChannel.pro_settings.tag_list = [];
}
......@@ -111,7 +106,6 @@ export class ProChannelService implements OnDestroy {
try {
const response = (await this.client.get(`api/v2/pro/channel/${id}`)) as {
channel;
me?;
};
this.currentChannel = response.channel;
......
......@@ -2,30 +2,19 @@ import { Cookie } from '../cookie';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { Location } from '@angular/common';
import { SiteService } from '../../common/services/site.service';
/**
* API Class
*/
export class Client {
base: string = '/';
origin: string = '';
cookie: Cookie = new Cookie();
static _(http: HttpClient, location: Location, site: SiteService) {
return new Client(http, location, site);
static _(http: HttpClient, location: Location) {
return new Client(http, location);
}
constructor(
public http: HttpClient,
public location: Location,
protected site: SiteService
) {
if (this.site.isProDomain) {
this.base = window.Minds.site_url;
this.origin = document.location.host;
}
}
constructor(public http: HttpClient, public location: Location) {}
/**
* Return a GET request
......@@ -66,21 +55,23 @@ export class Client {
getRaw(endpoint: string, data: Object = {}, options: Object = {}) {
endpoint += '?' + this.buildParams(data);
return new Promise((resolve, reject) => {
this.http.get(this.base + endpoint, this.buildOptions(options)).subscribe(
res => {
return resolve(res);
},
err => {
if (err.data && !err.data()) {
return reject(err || new Error('GET error'));
}
if (err.status === 401 && err.error.loggedin === false) {
window.location.href = '/login';
this.http
.get(this.base + endpoint, this.buildOptions(options, true))
.subscribe(
res => {
return resolve(res);
},
err => {
if (err.data && !err.data()) {
return reject(err || new Error('GET error'));
}
if (err.status === 401 && err.error.loggedin === false) {
window.location.href = '/login';
return reject(err);
}
return reject(err);
}
return reject(err);
}
);
);
});
}
......@@ -122,6 +113,40 @@ export class Client {
});
}
/**
* Return a POST request
*/
postRaw(url: string, data: Object = {}, options: Object = {}) {
return new Promise((resolve, reject) => {
this.http
.post(url, JSON.stringify(data), this.buildOptions(options, true))
.subscribe(
res => {
var data: any = res;
if (!data || data.status !== 'success') return reject(data);
return resolve(data);
},
err => {
if (err.data && !err.data()) {
return reject(err || new Error('POST error'));
}
if (err.status === 401 && err.error.loggedin === false) {
if (this.location.path() !== '/login') {
localStorage.setItem('redirect', this.location.path());
window.location.href = '/login';
}
return reject(err);
}
if (err.status !== 200) {
return reject(err.error);
}
}
);
});
}
/**
* Return a PUT request
*/
......@@ -199,7 +224,7 @@ export class Client {
/**
* Build the options
*/
private buildOptions(options: Object) {
private buildOptions(options: Object, withCredentials: boolean = false) {
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN') || '';
const headers = {
......@@ -207,19 +232,12 @@ export class Client {
'X-VERSION': environment.version,
};
if (this.origin) {
const PRO_XSRF_JWT = this.cookie.get('PRO-XSRF-JWT') || '';
headers['X-MINDS-ORIGIN'] = this.origin;
headers['X-PRO-XSRF-JWT'] = PRO_XSRF_JWT;
}
const builtOptions = {
headers: new HttpHeaders(headers),
cache: true,
};
if (this.origin) {
if (withCredentials) {
builtOptions['withCredentials'] = true;
}
......
import { Cookie } from '../cookie';
import { HttpClient } from '@angular/common/http';
import { SiteService } from '../../common/services/site.service';
/**
* API Class
*/
export class Upload {
base: string = '/';
origin: string = '';
cookie: Cookie = new Cookie();
static _(http: HttpClient, site: SiteService) {
return new Upload(http, site);
static _(http: HttpClient) {
return new Upload(http);
}
constructor(public http: HttpClient, protected site: SiteService) {
if (this.site.isProDomain) {
this.base = window.Minds.site_url;
this.origin = document.location.host;
}
}
constructor(public http: HttpClient) {}
/**
* Return a POST request
......@@ -81,16 +74,6 @@ export class Upload {
};
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN');
xhr.setRequestHeader('X-XSRF-TOKEN', XSRF_TOKEN);
if (this.origin) {
const PRO_XSRF_JWT = this.cookie.get('PRO-XSRF-JWT') || '';
xhr.withCredentials = true;
xhr.setRequestHeader('X-MINDS-ORIGIN', this.origin);
xhr.setRequestHeader('X-PRO-XSRF-JWT', PRO_XSRF_JWT);
}
xhr.send(formData);
});
}
......
......@@ -68,12 +68,12 @@ export const MINDS_PROVIDERS: any[] = [
{
provide: Client,
useFactory: Client._,
deps: [HttpClient, Location, SiteService],
deps: [HttpClient, Location],
},
{
provide: Upload,
useFactory: Upload._,
deps: [HttpClient, SiteService],
deps: [HttpClient],
},
{
provide: Storage,
......@@ -113,6 +113,7 @@ export const MINDS_PROVIDERS: any[] = [
{
provide: Session,
useFactory: Session._,
deps: [SiteService],
},
{
provide: ThirdPartyNetworksService,
......
......@@ -56,16 +56,30 @@ export class Session {
return false;
}
/**
* Emit login event
*/
login(user: any = null) {
//clear stale local storage
inject(user: any = null) {
// Clear stale localStorage
window.localStorage.clear();
// Emit new user info
this.userEmitter.next(user);
window.Minds.user = user;
if (user.admin === true) window.Minds.Admin = true;
// Set globals
window.Minds.LoggedIn = true;
window.Minds.user = user;
if (user.admin === true) {
window.Minds.Admin = true;
}
}
/**
* Inject user and emit login event
*/
login(user: any = null) {
this.inject(user);
this.loggedinEmitter.next(true);
}
......