mirror of https://github.com/abpframework/abp
commit
71bc369bca
@ -1,18 +0,0 @@
|
||||
import { ABP } from '../models';
|
||||
|
||||
export class SetLanguage {
|
||||
static readonly type = '[Session] Set Language';
|
||||
constructor(public payload: string, public dispatchAppConfiguration?: boolean) {}
|
||||
}
|
||||
export class SetTenant {
|
||||
static readonly type = '[Session] Set Tenant';
|
||||
constructor(public payload: ABP.BasicItem) {}
|
||||
}
|
||||
export class ModifyOpenedTabCount {
|
||||
static readonly type = '[Session] Modify Opened Tab Count';
|
||||
constructor(public operation: 'increase' | 'decrease') {}
|
||||
}
|
||||
export class SetRemember {
|
||||
static readonly type = '[Session] Set Remember';
|
||||
constructor(public payload: boolean) {}
|
||||
}
|
||||
@ -1,44 +1,71 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
import {
|
||||
SetLanguage,
|
||||
SetRemember,
|
||||
SetTenant,
|
||||
ModifyOpenedTabCount,
|
||||
} from '../actions/session.actions';
|
||||
import { SessionState } from '../states';
|
||||
import { ApplicationConfiguration } from '../models/application-configuration';
|
||||
import { Session } from '../models/session';
|
||||
import { InternalStore } from '../utils/internal-store-utils';
|
||||
import compare from 'just-compare';
|
||||
|
||||
export interface SessionDetail {
|
||||
openedTabCount: number;
|
||||
lastExitTime: number;
|
||||
remember: boolean;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SessionStateService {
|
||||
constructor(private store: Store) {}
|
||||
private readonly store = new InternalStore({} as Session.State);
|
||||
|
||||
getLanguage() {
|
||||
return this.store.selectSnapshot(SessionState.getLanguage);
|
||||
private updateLocalStorage = () => {
|
||||
localStorage.setItem('abpSession', JSON.stringify(this.store.state));
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
getTenant() {
|
||||
return this.store.selectSnapshot(SessionState.getTenant);
|
||||
private init() {
|
||||
const session = localStorage.getItem('abpSession');
|
||||
if (session) {
|
||||
this.store.patch(JSON.parse(session));
|
||||
}
|
||||
|
||||
this.store.sliceUpdate(state => state).subscribe(this.updateLocalStorage);
|
||||
}
|
||||
|
||||
onLanguageChange$() {
|
||||
return this.store.sliceUpdate(state => state.language);
|
||||
}
|
||||
|
||||
getSessionDetail() {
|
||||
return this.store.selectSnapshot(SessionState.getSessionDetail);
|
||||
onTenantChange$() {
|
||||
return this.store.sliceUpdate(state => state.tenant);
|
||||
}
|
||||
|
||||
dispatchSetLanguage(...args: ConstructorParameters<typeof SetLanguage>) {
|
||||
return this.store.dispatch(new SetLanguage(...args));
|
||||
getLanguage() {
|
||||
return this.store.state.language;
|
||||
}
|
||||
|
||||
getLanguage$() {
|
||||
return this.store.sliceState(state => state.language);
|
||||
}
|
||||
|
||||
getTenant() {
|
||||
return this.store.state.tenant;
|
||||
}
|
||||
|
||||
dispatchSetTenant(...args: ConstructorParameters<typeof SetTenant>) {
|
||||
return this.store.dispatch(new SetTenant(...args));
|
||||
getTenant$() {
|
||||
return this.store.sliceState(state => state.tenant);
|
||||
}
|
||||
|
||||
dispatchSetRemember(...args: ConstructorParameters<typeof SetRemember>) {
|
||||
return this.store.dispatch(new SetRemember(...args));
|
||||
setTenant(tenant: ApplicationConfiguration.CurrentTenant) {
|
||||
if (compare(tenant, this.store.state.tenant)) return;
|
||||
|
||||
this.store.patch({ tenant });
|
||||
}
|
||||
|
||||
dispatchModifyOpenedTabCount(...args: ConstructorParameters<typeof ModifyOpenedTabCount>) {
|
||||
return this.store.dispatch(new ModifyOpenedTabCount(...args));
|
||||
setLanguage(language: string) {
|
||||
if (language === this.store.state.language) return;
|
||||
|
||||
this.store.patch({ language });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
Action,
|
||||
Actions,
|
||||
ofActionSuccessful,
|
||||
Selector,
|
||||
State,
|
||||
StateContext,
|
||||
Store,
|
||||
} from '@ngxs/store';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import { fromEvent } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { GetAppConfiguration } from '../actions/config.actions';
|
||||
import {
|
||||
ModifyOpenedTabCount,
|
||||
SetLanguage,
|
||||
SetRemember,
|
||||
SetTenant,
|
||||
} from '../actions/session.actions';
|
||||
import { ABP, Session } from '../models';
|
||||
|
||||
@State<Session.State>({
|
||||
name: 'SessionState',
|
||||
defaults: { sessionDetail: { openedTabCount: 0 } } as Session.State,
|
||||
})
|
||||
@Injectable()
|
||||
export class SessionState {
|
||||
@Selector()
|
||||
static getLanguage({ language }: Session.State): string {
|
||||
return language;
|
||||
}
|
||||
|
||||
@Selector()
|
||||
static getTenant({ tenant }: Session.State): ABP.BasicItem {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
@Selector()
|
||||
static getSessionDetail({ sessionDetail }: Session.State): Session.SessionDetail {
|
||||
return sessionDetail;
|
||||
}
|
||||
|
||||
constructor(private oAuthService: OAuthService, private store: Store, private actions: Actions) {
|
||||
actions
|
||||
.pipe(ofActionSuccessful(GetAppConfiguration))
|
||||
.pipe(take(1))
|
||||
.subscribe(() => {
|
||||
const sessionDetail = this.store.selectSnapshot(SessionState)?.sessionDetail || {};
|
||||
|
||||
const fiveMinutesBefore = new Date().valueOf() - 5 * 60 * 1000;
|
||||
|
||||
if (
|
||||
sessionDetail.lastExitTime &&
|
||||
sessionDetail.openedTabCount === 0 &&
|
||||
this.oAuthService.hasValidAccessToken() &&
|
||||
sessionDetail.remember === false &&
|
||||
sessionDetail.lastExitTime < fiveMinutesBefore
|
||||
) {
|
||||
this.oAuthService.logOut();
|
||||
}
|
||||
|
||||
this.store.dispatch(new ModifyOpenedTabCount('increase'));
|
||||
|
||||
fromEvent(window, 'unload').subscribe(event => {
|
||||
this.store.dispatch(new ModifyOpenedTabCount('decrease'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Action(SetLanguage)
|
||||
setLanguage(
|
||||
{ patchState, dispatch }: StateContext<Session.State>,
|
||||
{ payload, dispatchAppConfiguration = true }: SetLanguage,
|
||||
) {
|
||||
patchState({
|
||||
language: payload,
|
||||
});
|
||||
|
||||
if (dispatchAppConfiguration) return dispatch(new GetAppConfiguration());
|
||||
}
|
||||
|
||||
@Action(SetTenant)
|
||||
setTenant({ patchState }: StateContext<Session.State>, { payload }: SetTenant) {
|
||||
patchState({
|
||||
tenant: payload,
|
||||
});
|
||||
}
|
||||
|
||||
@Action(SetRemember)
|
||||
setRemember(
|
||||
{ getState, patchState }: StateContext<Session.State>,
|
||||
{ payload: remember }: SetRemember,
|
||||
) {
|
||||
const { sessionDetail } = getState();
|
||||
|
||||
patchState({
|
||||
sessionDetail: {
|
||||
...sessionDetail,
|
||||
remember,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Action(ModifyOpenedTabCount)
|
||||
modifyOpenedTabCount(
|
||||
{ getState, patchState }: StateContext<Session.State>,
|
||||
{ operation }: ModifyOpenedTabCount,
|
||||
) {
|
||||
// tslint:disable-next-line: prefer-const
|
||||
let { openedTabCount, lastExitTime, ...detail } =
|
||||
getState().sessionDetail || ({ openedTabCount: 0 } as Session.SessionDetail);
|
||||
|
||||
if (operation === 'increase') {
|
||||
openedTabCount++;
|
||||
} else if (operation === 'decrease') {
|
||||
openedTabCount--;
|
||||
lastExitTime = new Date().valueOf();
|
||||
}
|
||||
|
||||
if (!openedTabCount || openedTabCount < 0) {
|
||||
openedTabCount = 0;
|
||||
}
|
||||
|
||||
patchState({
|
||||
sessionDetail: {
|
||||
openedTabCount,
|
||||
lastExitTime,
|
||||
...detail,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest';
|
||||
import { SessionStateService } from '../services/session-state.service';
|
||||
import { SessionState } from '../states/session.state';
|
||||
import { Store } from '@ngxs/store';
|
||||
import * as SessionActions from '../actions';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
|
||||
describe('SessionStateService', () => {
|
||||
let service: SessionStateService;
|
||||
let spectator: SpectatorService<SessionStateService>;
|
||||
let store: SpyObject<Store>;
|
||||
|
||||
const createService = createServiceFactory({
|
||||
service: SessionStateService,
|
||||
mocks: [Store, OAuthService],
|
||||
});
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
service = spectator.service;
|
||||
store = spectator.inject(Store);
|
||||
});
|
||||
test('should have the all SessionState static methods', () => {
|
||||
const reg = /(?<=static )(.*)(?=\()/gm;
|
||||
SessionState.toString()
|
||||
.match(reg)
|
||||
.forEach(fnName => {
|
||||
expect(service[fnName]).toBeTruthy();
|
||||
|
||||
const spy = jest.spyOn(store, 'selectSnapshot');
|
||||
spy.mockClear();
|
||||
|
||||
const isDynamicSelector = SessionState[fnName].name !== 'memoized';
|
||||
|
||||
if (isDynamicSelector) {
|
||||
SessionState[fnName] = jest.fn((...args) => args);
|
||||
service[fnName]('test', 0, {});
|
||||
expect(SessionState[fnName]).toHaveBeenCalledWith('test', 0, {});
|
||||
} else {
|
||||
service[fnName]();
|
||||
expect(spy).toHaveBeenCalledWith(SessionState[fnName]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('should have a dispatch method for every sessionState action', () => {
|
||||
const reg = /(?<=dispatch)(\w+)(?=\()/gm;
|
||||
SessionStateService.toString()
|
||||
.match(reg)
|
||||
.forEach(fnName => {
|
||||
expect(SessionActions[fnName]).toBeTruthy();
|
||||
|
||||
const spy = jest.spyOn(store, 'dispatch');
|
||||
spy.mockClear();
|
||||
|
||||
const params = Array.from(new Array(SessionActions[fnName].length));
|
||||
|
||||
service[`dispatch${fnName}`](...params);
|
||||
expect(spy).toHaveBeenCalledWith(new SessionActions[fnName](...params));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,70 +0,0 @@
|
||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
|
||||
import { Session } from '../models/session';
|
||||
import { LocalizationService, AuthService } from '../services';
|
||||
import { SessionState } from '../states';
|
||||
import { GetAppConfiguration } from '../actions/config.actions';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { Store, Actions } from '@ngxs/store';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
|
||||
export class DummyClass {}
|
||||
|
||||
export const SESSION_STATE_DATA = {
|
||||
language: 'tr',
|
||||
tenant: { id: 'd5692aef-2ac6-49cd-9f3e-394c0bd4f8b3', name: 'Test' },
|
||||
} as Session.State;
|
||||
|
||||
describe('SessionState', () => {
|
||||
let spectator: SpectatorService<DummyClass>;
|
||||
let state: SessionState;
|
||||
|
||||
const createService = createServiceFactory({
|
||||
service: DummyClass,
|
||||
mocks: [LocalizationService, Store, Actions, OAuthService],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
state = new SessionState(null, null, new Subject());
|
||||
});
|
||||
|
||||
describe('#getLanguage', () => {
|
||||
it('should return the current language', () => {
|
||||
expect(SessionState.getLanguage(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.language);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getTenant', () => {
|
||||
it('should return the tenant object', () => {
|
||||
expect(SessionState.getTenant(SESSION_STATE_DATA)).toEqual(SESSION_STATE_DATA.tenant);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SetLanguage', () => {
|
||||
it('should set the language and dispatch the GetAppConfiguration action', () => {
|
||||
let patchedData;
|
||||
let dispatchedData;
|
||||
const patchState = jest.fn(data => (patchedData = data));
|
||||
const dispatch = jest.fn(action => {
|
||||
dispatchedData = action;
|
||||
return of({});
|
||||
});
|
||||
|
||||
state.setLanguage({ patchState, dispatch } as any, { payload: 'en' }).subscribe();
|
||||
|
||||
expect(patchedData).toEqual({ language: 'en' });
|
||||
expect(dispatchedData instanceof GetAppConfiguration).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setTenantId', () => {
|
||||
it('should set the tenant', () => {
|
||||
let patchedData;
|
||||
const patchState = jest.fn(data => (patchedData = data));
|
||||
const testTenant = { id: '54ae02ba-9289-4c1b-8521-0ea437756288', name: 'Test Tenant' };
|
||||
state.setTenant({ patchState } as any, { payload: testTenant });
|
||||
|
||||
expect(patchedData).toEqual({ tenant: testTenant });
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in new issue