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 { 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