mirror of https://github.com/abpframework/abp.git
committed by
GitHub
34 changed files with 211 additions and 433 deletions
@ -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 { Injectable } from '@angular/core'; |
||||
import { Store } from '@ngxs/store'; |
import { ApplicationConfiguration } from '../models/application-configuration'; |
||||
import { |
import { Session } from '../models/session'; |
||||
SetLanguage, |
import { InternalStore } from '../utils/internal-store-utils'; |
||||
SetRemember, |
import compare from 'just-compare'; |
||||
SetTenant, |
|
||||
ModifyOpenedTabCount, |
export interface SessionDetail { |
||||
} from '../actions/session.actions'; |
openedTabCount: number; |
||||
import { SessionState } from '../states'; |
lastExitTime: number; |
||||
|
remember: boolean; |
||||
|
} |
||||
|
|
||||
@Injectable({ |
@Injectable({ |
||||
providedIn: 'root', |
providedIn: 'root', |
||||
}) |
}) |
||||
export class SessionStateService { |
export class SessionStateService { |
||||
constructor(private store: Store) {} |
private readonly store = new InternalStore({} as Session.State); |
||||
|
|
||||
getLanguage() { |
private updateLocalStorage = () => { |
||||
return this.store.selectSnapshot(SessionState.getLanguage); |
localStorage.setItem('abpSession', JSON.stringify(this.store.state)); |
||||
|
}; |
||||
|
|
||||
|
constructor() { |
||||
|
this.init(); |
||||
} |
} |
||||
|
|
||||
getTenant() { |
private init() { |
||||
return this.store.selectSnapshot(SessionState.getTenant); |
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() { |
onTenantChange$() { |
||||
return this.store.selectSnapshot(SessionState.getSessionDetail); |
return this.store.sliceUpdate(state => state.tenant); |
||||
} |
} |
||||
|
|
||||
dispatchSetLanguage(...args: ConstructorParameters<typeof SetLanguage>) { |
getLanguage() { |
||||
return this.store.dispatch(new SetLanguage(...args)); |
return this.store.state.language; |
||||
|
} |
||||
|
|
||||
|
getLanguage$() { |
||||
|
return this.store.sliceState(state => state.language); |
||||
|
} |
||||
|
|
||||
|
getTenant() { |
||||
|
return this.store.state.tenant; |
||||
} |
} |
||||
|
|
||||
dispatchSetTenant(...args: ConstructorParameters<typeof SetTenant>) { |
getTenant$() { |
||||
return this.store.dispatch(new SetTenant(...args)); |
return this.store.sliceState(state => state.tenant); |
||||
} |
} |
||||
|
|
||||
dispatchSetRemember(...args: ConstructorParameters<typeof SetRemember>) { |
setTenant(tenant: ApplicationConfiguration.CurrentTenant) { |
||||
return this.store.dispatch(new SetRemember(...args)); |
if (compare(tenant, this.store.state.tenant)) return; |
||||
|
|
||||
|
this.store.patch({ tenant }); |
||||
} |
} |
||||
|
|
||||
dispatchModifyOpenedTabCount(...args: ConstructorParameters<typeof ModifyOpenedTabCount>) { |
setLanguage(language: string) { |
||||
return this.store.dispatch(new ModifyOpenedTabCount(...args)); |
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