@tapsellorg/angular-material-library
Version:
Angular library for Tapsell
1 lines • 85.8 kB
Source Map (JSON)
{"version":3,"file":"tapsellorg-angular-material-library-sso.mjs","sources":["../../projects/pegah-library/sso/sso-logic/constants.ts","../../projects/pegah-library/sso/sso-logic/config.ts","../../projects/pegah-library/sso/sso-logic/sso.utils.ts","../../projects/pegah-library/sso/sso-logic/sso-storage.service.ts","../../projects/pegah-library/sso/sso-logic/sso.service.ts","../../projects/pegah-library/sso/services-shortcut/constants.ts","../../projects/pegah-library/sso/authorized-link/authorized-link.directive.ts","../../projects/pegah-library/sso/services-shortcut/services-shortcut.component.ts","../../projects/pegah-library/sso/services-shortcut/services-shortcut.component.html","../../projects/pegah-library/sso/authorized-link/authorized-link.pipe.ts","../../projects/pegah-library/sso/authorized-link/authorized-link.module.ts","../../projects/pegah-library/sso/services-shortcut/services-shortcut.module.ts","../../projects/pegah-library/sso/sso-logic/sso-redirect.service.ts","../../projects/pegah-library/sso/auth-guard/authorized.guard.ts","../../projects/pegah-library/sso/user-menu/user-menu.component.ts","../../projects/pegah-library/sso/user-menu/user-menu.component.html","../../projects/pegah-library/sso/user-menu/user-menu.module.ts","../../projects/pegah-library/sso/interceptors/token.interceptor.ts","../../projects/pegah-library/sso/login-as-user/login-as-user.directive.ts","../../projects/pegah-library/sso/login-as-user/login-as-user.module.ts","../../projects/pegah-library/sso/tapsellorg-angular-material-library-sso.ts"],"sourcesContent":["import { PghSsoAuthDataRefreshTokenRequestModel, PghSsoConfig } from './models';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { PghSsoImpersonateData } from '../login-as-user/login-as-user.model';\n\nexport const PGH_REFRESH_TOKEN_REQUEST = (\n http: HttpClient,\n refreshToken: string,\n config: PghSsoConfig,\n) => {\n const refreshTokenUrl = `${config.ssoApiBaseUrl}/sso/flow/refresh_token`;\n const payload = new HttpParams().set('refreshToken', refreshToken);\n return http.post<PghSsoAuthDataRefreshTokenRequestModel>(refreshTokenUrl, payload);\n};\n\nexport const PGH_GET_UUID_REQUEST = (\n http: HttpClient,\n authData: PghSsoImpersonateData,\n config: PghSsoConfig,\n) => {\n const payload = new HttpParams()\n .set('refresh_token', authData.refresh_token)\n .set('access_token', authData.access_token);\n\n const getUUidUrl = `${config.ssoApiBaseUrl}/sso/token2uuid`;\n return http.post<string>(getUUidUrl, payload, {\n withCredentials: true,\n });\n};\n\nexport const PGH_GET_TOKEN_REQUEST = (http: HttpClient, uuid: string, config: PghSsoConfig) => {\n const getUUidUrl = `${config.ssoApiBaseUrl}/sso/uuid2token/${uuid}`;\n return http.get<PghSsoImpersonateData>(getUUidUrl);\n};\n\nexport const PGH_TRUSTED_DOMAINS = [\n 'https://web.tapsell.com',\n 'https://app.tapsell.com',\n 'https://admin-web.tapsell.com',\n 'https://admin-app.tapsell.com',\n 'https://gravity.pegah.tech',\n];\n\nexport const PGH_EXPIRE_TOKEN_AFTER_LOGOUT = (\n http: HttpClient,\n authData: PghSsoImpersonateData,\n config: PghSsoConfig,\n) => {\n const payload = new HttpParams()\n .set('refresh_token', authData.refresh_token)\n .set('access_token', authData.access_token);\n\n const getUUidUrl = `${config.ssoApiBaseUrl}/sso/logout`;\n return http.post<string>(getUUidUrl, payload, {\n withCredentials: true,\n });\n};\n","import { inject, InjectionToken } from '@angular/core';\nimport { PghSsoConfig } from './models';\nimport {\n PGH_EXPIRE_TOKEN_AFTER_LOGOUT,\n PGH_GET_TOKEN_REQUEST,\n PGH_GET_UUID_REQUEST,\n PGH_REFRESH_TOKEN_REQUEST,\n PGH_TRUSTED_DOMAINS,\n} from './constants';\nimport { HttpClient } from '@angular/common/http';\n\nexport const PGH_SSO_CONFIG = new InjectionToken<Partial<PghSsoConfig>>('sso-config', {\n providedIn: 'root',\n factory: () => ({}),\n});\n\n/**\n * PGH_SSO_CONFIG is a partial version of PGH_SSO_ENRICHED_CONFIG so that it'll merge it with the default values\n */\nexport const PGH_SSO_ENRICHED_CONFIG = new InjectionToken<PghSsoConfig>('sso-enriched-config', {\n providedIn: 'root',\n factory: () => {\n const httpClient = inject(HttpClient);\n const ssoConfig = inject(PGH_SSO_CONFIG);\n const result: PghSsoConfig = {\n ssoPanelUrl: 'https://sso-panel.pegah.tech',\n ssoApiBaseUrl: 'https://sso-api.pegah.tech',\n restrictedUrls: ['https://unified-api.pegah.tech'],\n impersonateApiUrl: 'https://sso-api.pegah.tech/impersonate/login',\n sharedIframeUrl: 'https://auth-client.tapsell.ir',\n isInsideSsoPanel: false,\n changePasswordRoute: '/change-password',\n profileRoute: '/profile',\n trustedDomains: PGH_TRUSTED_DOMAINS,\n isDevMode: false,\n redirectToSsoAfterLogout: true,\n pghCookieStoreDomain: 'tapsell.ir',\n pghRedirectUrlParam: 'redirect',\n refreshTokenRequest: refreshToken =>\n PGH_REFRESH_TOKEN_REQUEST(httpClient, refreshToken, result),\n getUUidRequest: authData => PGH_GET_UUID_REQUEST(httpClient, authData, result),\n getTokenRequest: uuid => PGH_GET_TOKEN_REQUEST(httpClient, uuid, result),\n expireTokenAfterLogout: authData =>\n PGH_EXPIRE_TOKEN_AFTER_LOGOUT(httpClient, authData, result),\n accessTokenExpiredError: err => err === 'webApi.unAuthorized.ExpiredAccessToken',\n refreshTokenExpiredError: err => err === 'iam.unAuthorized.ExpiredAccessToken',\n ...ssoConfig,\n };\n return result;\n },\n});\n","import {\n PghErrorResponse,\n PghSsoAuthData,\n PghSsoIframeAuthData,\n PghSsoIframeLogout,\n PghSsoIframeMessage,\n PghTokenPayload,\n} from './models';\n\n// @dynamic\nexport class PghSsoUtils {\n static readonly AUTH_DATA_REQUIRED_KEYS: (keyof PghSsoAuthData)[] = [\n 'accessToken',\n 'refreshToken',\n ];\n static readonly AUTH_DATA_KEYS: (keyof PghSsoAuthData)[] = ['accessToken', 'refreshToken'];\n static readonly UUID_PARAM = 'uuid';\n\n static validateAuthData(data: Partial<PghSsoAuthData>): data is PghSsoAuthData {\n return (\n this.AUTH_DATA_REQUIRED_KEYS.reduce<boolean>((res, key) => res && !!data[key], true) &&\n data.accessToken?.split('.').length === 3\n );\n }\n\n static isTokenMessage(data: PghSsoIframeMessage): data is PghSsoIframeAuthData {\n return data.type === 'sso:auth';\n }\n static isLogoutMessage(data: PghSsoIframeMessage): data is PghSsoIframeLogout {\n return data.type === 'sso:logout';\n }\n\n static decodeJwtToken(accessToken: string): PghTokenPayload {\n const base64Url = accessToken.split('.')[1];\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n\n return JSON.parse(jsonPayload);\n }\n\n static parseError(error: unknown): PghErrorResponse {\n if (typeof error === 'string') {\n try {\n return JSON.parse(error) as PghErrorResponse;\n } catch {\n throw new Error('Invalid JSON error response');\n }\n }\n if (typeof error === 'object' && error !== null) {\n return error as PghErrorResponse;\n }\n throw new Error('Unsupported error format');\n }\n}\n","import { Inject, Injectable, OnDestroy } from '@angular/core';\nimport Cookies from 'js-cookie';\nimport { PghStorageFactory } from '@tapsellorg/angular-material-library/src/lib/common';\nimport { PghSsoConfig, PghSsoIframeMessage } from './models';\nimport { interval, Subject, timer } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { PGH_SSO_ENRICHED_CONFIG } from './config';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PghSsoStorageService implements OnDestroy {\n private localStorageFactory = PghStorageFactory.localStorageFactory();\n private sharedIframe!: HTMLIFrameElement; // in initSharedIframe\n iframeIncomingMessages$ = new Subject<PghSsoIframeMessage>();\n isIframeLoaded = false;\n\n constructor(@Inject(PGH_SSO_ENRICHED_CONFIG) private readonly ssoConfig: PghSsoConfig) {\n this.setupIframeEventListeners();\n this.initSharedIframe();\n this.sharedIframe.addEventListener('load', () => (this.isIframeLoaded = true));\n }\n\n getDataFromStorage(key: string) {\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n return this.localStorageFactory.getItem(key) || Cookies.get(key);\n }\n\n setDataInStorage(key: string, value: string | undefined) {\n if (!value) return;\n if (this.getDataFromStorage(key) === value) return;\n\n this.storeCookieSecureCrossSite(key, value);\n this.storeInLocalStorage(key, value);\n }\n\n deleteDataFromStorage(key: string) {\n this.deleteCookie(key);\n this.deleteFromLocalStorage(key);\n }\n\n private storeCookieSecureCrossSite(key: string, value: string) {\n Cookies.set(key, value, {\n domain: this.ssoConfig.pghCookieStoreDomain,\n path: '/',\n sameSite: 'None',\n secure: true,\n expires: new Date(9999, 0),\n });\n }\n private deleteCookie(key: string) {\n Cookies.remove(key, {\n domain: this.ssoConfig.pghCookieStoreDomain,\n path: '/',\n sameSite: 'None',\n secure: true,\n });\n }\n\n private storeInLocalStorage(key: string, value: string) {\n this.localStorageFactory.setItem(key, value);\n }\n private deleteFromLocalStorage(key: string) {\n this.localStorageFactory.removeItem(key);\n }\n\n private readonly IFRAME_ID = 'tapsell-sso-shared';\n\n private initSharedIframe() {\n this.sharedIframe = document.getElementById(this.IFRAME_ID) as HTMLIFrameElement;\n if (this.sharedIframe) {\n this.isIframeLoaded = true;\n return;\n }\n\n this.sharedIframe = document.createElement('iframe');\n this.sharedIframe.src = this.ssoConfig.sharedIframeUrl;\n this.sharedIframe.style.setProperty('display', 'none');\n this.sharedIframe.id = this.IFRAME_ID;\n this.sharedIframe.height = '0';\n this.sharedIframe.name = this.IFRAME_ID;\n this.sharedIframe.setAttribute('role', 'none');\n document.body.appendChild(this.sharedIframe);\n }\n\n postMessageToIframe(data: PghSsoIframeMessage) {\n if (!this.isIframeLoaded) {\n const _interval$ = interval(50)\n .pipe(takeUntil(timer(1000)))\n .subscribe(() => {\n if (this.isIframeLoaded) {\n this.postMessageToIframe(data);\n _interval$.unsubscribe();\n }\n });\n return;\n }\n this.sharedIframe.contentWindow?.postMessage({ tapsell: data }, '*'); // todo targetOrigin\n }\n\n private setupIframeEventListeners() {\n window.addEventListener('message', this.iframeMessageListener);\n }\n\n private iframeMessageListener = ({ data, origin, source }: MessageEvent) => {\n if (origin !== this.ssoConfig.sharedIframeUrl || source !== this.sharedIframe.contentWindow)\n return;\n // console.log('#ee data, origin, source', data, origin, source);\n this.iframeIncomingMessages$.next(data.tapsell);\n };\n\n ngOnDestroy() {\n window.removeEventListener('message', this.iframeMessageListener);\n }\n}\n","import { Inject, Injectable } from '@angular/core';\nimport { Router } from '@angular/router';\nimport {\n ObjectUtils,\n UrlUtils,\n withDestroy,\n} from '@tapsellorg/angular-material-library/src/lib/common';\nimport { BehaviorSubject, concatMap, interval, last, of, race, takeWhile, timer } from 'rxjs';\nimport { filter, map, switchMap, takeUntil } from 'rxjs/operators';\nimport { PGH_SSO_ENRICHED_CONFIG } from './config';\nimport {\n PghSsoAuthData,\n PghSsoAuthEnrichedData,\n PghSsoConfig,\n PghSsoIframeAuthData,\n PghSsoIframeLogout,\n} from './models';\nimport { PghSsoStorageService } from './sso-storage.service';\nimport { PghSsoUtils } from './sso.utils';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PghSsoService extends withDestroy<any>() {\n private _authData$ = new BehaviorSubject<PghSsoAuthData | undefined>(undefined);\n authData$ = this._authData$.pipe(\n map<PghSsoAuthData | undefined, PghSsoAuthEnrichedData | undefined>(authData => {\n if (!authData || !PghSsoUtils.validateAuthData(authData)) return;\n const {\n userAccountData,\n adminAccountData,\n sub: email,\n } = PghSsoUtils.decodeJwtToken(authData.accessToken);\n return {\n ...authData,\n userId: userAccountData?.userId,\n email,\n impersonatedByAdmin: !!adminAccountData?.impersonated,\n };\n }),\n );\n\n isLoggedIn$ = race(\n this.authData$.pipe(filter(d => !!d)),\n interval(100).pipe(\n takeWhile(() => !this.ssoStorageService.isIframeLoaded, true),\n takeUntil(timer(1000)),\n last(),\n ),\n ).pipe(switchMap(_ => this.authData$.pipe(map(d => !!d))));\n\n authData?: PghSsoAuthEnrichedData;\n lastAuthStorePriority = 0;\n\n constructor(\n @Inject(PGH_SSO_ENRICHED_CONFIG) private readonly ssoConfig: PghSsoConfig,\n private ssoStorageService: PghSsoStorageService,\n private router: Router,\n ) {\n super();\n this.setupIframeEventListeners();\n this.retrieveTokenFromStorage();\n this.retrieveTokenFromUrl();\n\n this.authData$.pipe(takeUntil(this._destroyed$)).subscribe(data => {\n this.authData = data;\n });\n }\n\n private retrieveTokenFromStorage() {\n const partialAuthData: Partial<PghSsoAuthData> = {\n accessToken: this.ssoStorageService.getDataFromStorage('accessToken'),\n refreshToken: this.ssoStorageService.getDataFromStorage('refreshToken'),\n };\n\n if (PghSsoUtils.validateAuthData(partialAuthData)) {\n this._authData$.next(partialAuthData as PghSsoAuthData);\n }\n }\n\n private retrieveTokenFromUrl() {\n const params: Partial<PghSsoAuthData> = {};\n new URLSearchParams(location.search).forEach((value, key) => {\n params[key as keyof PghSsoAuthData] = value;\n });\n if (!PghSsoUtils.validateAuthData(params)) return;\n\n this.storeAuthData(params, 1);\n UrlUtils.removeQueryParams(this.router, PghSsoUtils.AUTH_DATA_KEYS);\n }\n\n private setupIframeEventListeners() {\n let priority = 1;\n this.ssoStorageService.iframeIncomingMessages$\n .pipe(\n takeUntil(this._destroyed$),\n concatMap((value, index) => {\n priority = index === 0 ? 0 : 1; // first emit has less priority\n return of(value);\n }),\n )\n .subscribe(data => {\n // console.log('#ee iframe message', data);\n if (PghSsoUtils.isTokenMessage(data)) {\n if (!data.value) return;\n if (!PghSsoUtils.validateAuthData(data.value)) return;\n\n this.storeAuthData(data.value, priority);\n } else if (PghSsoUtils.isLogoutMessage(data)) {\n // Check for UUID in the URL\n const uuid = new URLSearchParams(location.search).get(PghSsoUtils.UUID_PARAM);\n if (!uuid) {\n this.logout(priority);\n }\n }\n });\n }\n\n storeAuthData(data: PghSsoAuthData, priority: number = 1) {\n // console.log('#ee store auth data', data, priority, this.lastAuthStorePriority);\n if (!PghSsoUtils.validateAuthData(data)) return;\n\n if (priority < this.lastAuthStorePriority) return;\n this.lastAuthStorePriority = priority;\n\n if (this.authData && ObjectUtils.isEqualObject(data, this.authData)) return;\n this._authData$.next(data);\n\n // console.log('#ee data', data);\n this.ssoStorageService.postMessageToIframe({\n type: 'sso:auth',\n value: data,\n } as PghSsoIframeAuthData);\n\n PghSsoUtils.AUTH_DATA_KEYS.forEach(key => {\n this.ssoStorageService.setDataInStorage(key, data[key]);\n });\n }\n\n logout(priority: number = 1) {\n // console.log('#ee logout', priority, this.lastAuthStorePriority);\n if (priority < this.lastAuthStorePriority) return;\n this.lastAuthStorePriority = priority;\n\n if (!this.authData) return;\n this.ssoConfig\n .expireTokenAfterLogout({\n access_token: this.authData.accessToken,\n refresh_token: this.authData.refreshToken,\n })\n .pipe(\n switchMap(() => {\n this._authData$.next(undefined);\n this.ssoStorageService.postMessageToIframe({\n type: 'sso:logout',\n value: {},\n } as PghSsoIframeLogout);\n\n PghSsoUtils.AUTH_DATA_KEYS.forEach(key => {\n this.ssoStorageService.deleteDataFromStorage(key);\n });\n\n if (this.ssoConfig.redirectToSsoAfterLogout) {\n this.redirectToLoginPageInSso();\n }\n return of(null);\n }),\n )\n .subscribe();\n }\n\n redirectToLoginPageInSso() {\n if (this.ssoConfig.isInsideSsoPanel) {\n this.router.navigate(['/login']);\n } else {\n location.href = `${this.ssoConfig.ssoPanelUrl}?${\n this.ssoConfig.pghRedirectUrlParam\n }=${encodeURIComponent(location.href)}`;\n }\n }\n}\n","import { PghServiceShortcut } from './models';\n\nexport const PGH_SERVICES_SHORTCUTS_DEFAULT: PghServiceShortcut[] = [\n {\n link: 'https://sso-panel.pegah.tech',\n name: 'SSO',\n desc: 'مدیریت حساب',\n logo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/color/Icon+50px.svg',\n quickLinks: [\n {\n link: 'https://sso-panel.pegah.tech/profile',\n name: 'پروفایل و حساب',\n exposeTokenInUrl: false,\n },\n {\n link: 'https://sso-panel.pegah.tech/financial-management',\n name: 'مدیریت مالی',\n exposeTokenInUrl: false,\n },\n {\n link: 'https://sso-panel.pegah.tech/financial-management',\n name: 'افزایش اعتبار',\n exposeTokenInUrl: false,\n },\n ],\n exposeTokenInUrl: false,\n },\n {\n link: 'https://dashboard.tapsell.ir',\n name: 'تپسل',\n desc: 'پلتفرم تبلیغات موبایل',\n logo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/blue/Icon+50px.svg',\n nightLogo:\n 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/blue/Icon+50px.svg',\n quickLinks: [\n { link: 'https://dashboard.tapsell.ir/campaigns', name: 'کمپینها', exposeTokenInUrl: false },\n { link: 'https://dashboard.tapsell.ir/apps', name: 'اپلیکیشنها', exposeTokenInUrl: false },\n { link: 'https://college.tapsell.ir/', name: 'کالج', exposeTokenInUrl: false },\n ],\n exposeTokenInUrl: false,\n },\n {\n link: 'https://panel.mediaad.org',\n name: 'مدیااد',\n desc: 'پلتفرم تبلیغات وب',\n logo: 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/green/Icon+50px.svg',\n nightLogo:\n 'https://cdn.tapture.ir/tapsell/web/front/web-logo/tapsell-new/icons/circle/colored/green/Icon+50px.svg',\n quickLinks: [\n { link: 'https://panel.mediaad.org/campaigns', name: 'کمپینها', exposeTokenInUrl: false },\n ],\n exposeTokenInUrl: false,\n },\n];\n","import { Directive, ElementRef, HostListener, Inject, input } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { PghSsoService } from '../sso-logic/sso.service';\nimport { PGH_SSO_ENRICHED_CONFIG } from '../sso-logic/config';\nimport { PghSsoConfig } from '../sso-logic/models';\nimport { PghSsoUtils } from '../sso-logic/sso.utils';\nimport { UrlUtils } from '@tapsellorg/angular-material-library/src/lib/common';\n\n@Directive({\n selector: 'a[pghAuthorizedLink]',\n standalone: false,\n})\nexport class PghAuthorizedLinkDirective {\n exposeTokenInUrl = input<boolean>(true);\n\n constructor(\n private router: Router,\n private ssoService: PghSsoService,\n private elementRef: ElementRef<HTMLAnchorElement>,\n @Inject(PGH_SSO_ENRICHED_CONFIG) private readonly ssoConfig: PghSsoConfig,\n ) {}\n\n @HostListener('click', ['$event']) onClick(event: Event) {\n event.preventDefault();\n\n const linkElement = this.elementRef.nativeElement;\n const href = linkElement.getAttribute('href');\n const target = linkElement.getAttribute('target') ?? '_self';\n\n if (!href || !this.ssoService.authData) return;\n\n const { accessToken, refreshToken } = this.ssoService.authData;\n const isInternal = linkElement.hostname === window.location.hostname;\n const mappedTokenData = {\n access_token: accessToken,\n refresh_token: refreshToken,\n };\n\n if (isInternal) {\n const url = UrlUtils.removeOrigin(href);\n this.router.navigateByUrl(url).then();\n return;\n }\n (this.exposeTokenInUrl()\n ? of(UrlUtils.mergeParamsToUrl(href, { accessToken, refreshToken }))\n : this.ssoConfig\n .getUUidRequest(mappedTokenData)\n .pipe(map(uuid => UrlUtils.mergeParamsToUrl(href, { [PghSsoUtils.UUID_PARAM]: uuid })))\n ).subscribe({\n next: newUrl => window.open(newUrl, target),\n error: () => {\n console.error('Failed to fetch UUID for authorized link.');\n },\n });\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n input,\n OnChanges,\n OnInit,\n signal,\n SimpleChanges,\n ViewEncapsulation,\n} from '@angular/core';\nimport { PghServiceShortcut } from './models';\nimport {\n PghIndicatorColor,\n PghIndicatorPersistData,\n} from '@tapsellorg/angular-material-library/src/lib/indicator';\nimport { PGH_SERVICES_SHORTCUTS_DEFAULT } from './constants';\nimport { PghDarkModeService } from '@tapsellorg/angular-material-library/src/lib/common';\n\n@Component({\n selector: 'pgh-services-shortcut',\n templateUrl: './services-shortcut.component.html',\n styleUrls: ['./services-shortcut.component.scss'],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: false,\n})\nexport class PghServicesShortcutComponent implements OnInit, OnChanges {\n hideIndicator = input<boolean>(false);\n indicatorColor = input<PghIndicatorColor | undefined>('primary');\n servicesShortcuts = input<PghServiceShortcut[]>(PGH_SERVICES_SHORTCUTS_DEFAULT);\n isAdmin = input<boolean>(false);\n\n constructor(private darkModeService: PghDarkModeService) {}\n\n services = signal<(PghServiceShortcut & { isCurrentService: boolean })[]>([]);\n\n indicatorPersistData = signal<PghIndicatorPersistData>({\n persistId: 'services-shortcut',\n persistDays: 7,\n });\n\n ngOnInit() {\n this.setServicesShortcuts();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.servicesShortcuts) {\n this.setServicesShortcuts();\n }\n }\n\n setServicesShortcuts() {\n this.services.set(\n this.servicesShortcuts().map(service => ({\n ...service,\n isCurrentService: service.link.includes(location.hostname),\n })),\n );\n }\n\n get isDarkModeEnabled() {\n return this.darkModeService.isDarkModeEnabled;\n }\n}\n","<pgh-overlay\n [origin]=\"servicesMenuOriginRef\"\n #servicesMenuRef\n [panelClasses]=\"['pgh-services-shortcut-menu']\"\n>\n <ng-template #pghOverlayContent>\n <a\n *ngFor=\"let s of services()\"\n pghAuthorizedLink\n [href]=\"s.link\"\n [exposeTokenInUrl]=\"s.exposeTokenInUrl\"\n target=\"_blank\"\n class=\"pgh-service-shortcut-item text-gray\"\n [class.is-active]=\"s.isCurrentService\"\n matRipple\n >\n <div class=\"pgh-service-item-img\">\n <img\n class=\"max-width-100\"\n [src]=\"isDarkModeEnabled ? (s.nightLogo ? s.nightLogo : s.logo) : s.logo\"\n [alt]=\"s.name\"\n />\n </div>\n <div class=\"pgh-service-item-name d-flex align-items-center\">\n <h2 class=\"mb-0 font-weight-bold text-gray-700\">{{ s.name }}</h2>\n <ng-container *ngIf=\"s.desc\">\n <span class=\"mx-2\">-</span>\n <span class=\"text-gray\">{{ s.desc }}</span>\n </ng-container>\n </div>\n <div class=\"pgh-service-item-quick-links d-flex\">\n <a\n *ngFor=\"let q of s.quickLinks\"\n pghAuthorizedLink\n [href]=\"q.link\"\n target=\"_blank\"\n [exposeTokenInUrl]=\"s.exposeTokenInUrl\"\n >\n <pgh-status-label\n [showIcon]=\"false\"\n [label]=\"q.name\"\n labelSize=\"mini\"\n type=\"gray\"\n ></pgh-status-label>\n </a>\n </div>\n </a>\n </ng-template>\n</pgh-overlay>\n\n<button\n mat-button\n cdkOverlayOrigin\n #servicesMenuOriginRef=\"cdkOverlayOrigin\"\n (click)=\"servicesMenuRef.open()\"\n>\n <mat-icon\n svgIcon=\"apps\"\n pghIndicator\n [pghIndicatorHidden]=\"hideIndicator()\"\n [pghIndicatorColor]=\"indicatorColor()\"\n [pghIndicatorPersistData]=\"indicatorPersistData()\"\n [pghIndicatorPosition]=\"'top start'\"\n ></mat-icon>\n <ng-container *ngIf=\"isAdmin(); else userServices\">\n <span *ifMedia=\"'sm'\">{{ 'SSO_ADMIN_SERVICES_SHORTCUT_TITLE' | translate }}</span>\n </ng-container>\n <ng-template #userServices>\n <span *ifMedia=\"'sm'\">{{ 'SSO_SERVICES_SHORTCUT_TITLE' | translate }}</span>\n </ng-template>\n</button>\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PghSsoService } from '../sso-logic/sso.service';\nimport { UrlUtils } from '@tapsellorg/angular-material-library/src/lib/common';\n\n@Pipe({\n name: 'pghAuthorizedLink',\n standalone: false,\n})\nexport class PghAuthorizedLinkPipe implements PipeTransform {\n constructor(private ssoService: PghSsoService) {}\n transform(link: string | undefined | null): unknown {\n if (!link) return link;\n if (!this.ssoService.authData) return link;\n\n return UrlUtils.mergeParamsToUrl(link, this.ssoService.authData as any);\n }\n}\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { PghAuthorizedLinkDirective } from './authorized-link.directive';\nimport { PghAuthorizedLinkPipe } from './authorized-link.pipe';\n\n@NgModule({\n declarations: [PghAuthorizedLinkDirective, PghAuthorizedLinkPipe],\n imports: [CommonModule],\n exports: [PghAuthorizedLinkDirective],\n})\nexport class PghSsoAuthorizedLinkModule {}\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { PghServicesShortcutComponent } from './services-shortcut.component';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\nimport { PghOverlayModule } from '@tapsellorg/angular-material-library/src/lib/overlay';\nimport { PghStatusLabelModule } from '@tapsellorg/angular-material-library/src/lib/status-label';\nimport { MatRippleModule } from '@angular/material/core';\nimport { PghSsoAuthorizedLinkModule } from '../authorized-link/authorized-link.module';\nimport { PghIndicatorModule } from '@tapsellorg/angular-material-library/src/lib/indicator';\nimport { PghMediaModule } from '@tapsellorg/angular-material-library/media';\nimport { TranslateModule } from '@tapsellorg/angular-material-library/src/lib/translate';\n\n@NgModule({\n declarations: [PghServicesShortcutComponent],\n imports: [\n CommonModule,\n MatIconModule,\n MatButtonModule,\n PghOverlayModule,\n PghStatusLabelModule,\n MatRippleModule,\n PghSsoAuthorizedLinkModule,\n PghIndicatorModule,\n PghMediaModule,\n TranslateModule,\n ],\n exports: [PghServicesShortcutComponent],\n})\nexport class PghServicesShortcutModule {}\n","import { Inject, Injectable } from '@angular/core';\nimport { ActivatedRoute, Router, UrlTree } from '@angular/router';\nimport { UrlUtils, withDestroy } from '@tapsellorg/angular-material-library/src/lib/common';\nimport { takeUntil } from 'rxjs/operators';\nimport { PghSsoService } from './sso.service';\nimport { PghLoginQueryParams, PghSsoConfig } from './models';\nimport { PGH_SSO_ENRICHED_CONFIG } from './config';\nimport { PghSsoUtils } from './sso.utils';\nimport { PghSsoImpersonateData } from '../login-as-user/login-as-user.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PghSsoRedirectService extends withDestroy() {\n redirectUrl?: string;\n\n constructor(\n private activatedRoute: ActivatedRoute,\n private router: Router,\n private pghSsoService: PghSsoService,\n @Inject(PGH_SSO_ENRICHED_CONFIG) private readonly ssoConfig: PghSsoConfig,\n ) {\n super();\n\n this.setupRedirectUrlRetriever();\n }\n\n private setupRedirectUrlRetriever() {\n const params: Partial<PghLoginQueryParams> = {};\n new URLSearchParams(location.search).forEach((value, key) => {\n params[key as keyof PghLoginQueryParams] = value;\n });\n this.retrieveRedirectUrl(params);\n this.activatedRoute.queryParams.pipe(takeUntil(this._destroyed$)).subscribe(queryParams => {\n this.retrieveRedirectUrl(queryParams as PghLoginQueryParams);\n });\n }\n\n retrieveRedirectUrl(queryParams: PghLoginQueryParams) {\n const { redirect } = queryParams;\n if (!redirect) return;\n\n try {\n const redirectUrl = new URL(redirect.trim());\n const trustedOrigins = this.ssoConfig.trustedDomains.map(url => new URL(url).origin);\n const { isDevMode } = this.ssoConfig;\n const isLocalhost =\n isDevMode && ['localhost', '127.0.0.1', '::1'].includes(redirectUrl.hostname);\n const isTrusted = isLocalhost || trustedOrigins.includes(redirectUrl.origin);\n\n if (!isTrusted) return;\n\n this.redirectUrl = redirectUrl.href;\n return this.redirectUrl;\n } catch (error) {\n // If the redirect URL is invalid or malformed, silently ignore\n return;\n }\n }\n\n retrieveUuidAndNavigate(tokenData: PghSsoImpersonateData) {\n this.ssoConfig.getUUidRequest(tokenData).subscribe({\n next: uuid => {\n const uuidParam = { [PghSsoUtils.UUID_PARAM]: uuid };\n location.href = UrlUtils.mergeParamsToUrl(this.redirectUrl!, uuidParam);\n },\n error: error => {\n console.error('UUID retrieval failed', error);\n },\n });\n }\n\n /**\n * @param useParseUrl: used for guards\n */\n navigateToRetrievedRedirectUrl(useParseUrl: true): UrlTree;\n navigateToRetrievedRedirectUrl(useParseUrl: false): void;\n navigateToRetrievedRedirectUrl(): void;\n navigateToRetrievedRedirectUrl(useParseUrl = false) {\n const routerNavigateFunction = (route: string) =>\n useParseUrl ? this.router.parseUrl(route) : this.router.navigateByUrl(route);\n\n if (!this.redirectUrl) {\n return routerNavigateFunction('/');\n }\n\n const redirectUrlParsed = new URL(this.redirectUrl);\n\n if (redirectUrlParsed.origin === location.origin) {\n return routerNavigateFunction(redirectUrlParsed.pathname);\n }\n\n if (!this.pghSsoService.authData) {\n location.href = this.redirectUrl;\n return false;\n }\n\n const tokenData: PghSsoImpersonateData = {\n access_token: this.pghSsoService.authData.accessToken,\n refresh_token: this.pghSsoService.authData.refreshToken,\n };\n\n this.retrieveUuidAndNavigate(tokenData);\n return false;\n }\n}\n","import { Inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { PghSsoService } from '../sso-logic/sso.service';\nimport { catchError, map, switchMap, take } from 'rxjs/operators';\nimport { PghLoginQueryParams, PghSsoConfig } from '../sso-logic/models';\nimport { PGH_SSO_ENRICHED_CONFIG } from '../sso-logic/config';\nimport { PghSsoImpersonateData } from '../login-as-user/login-as-user.model';\nimport { PghSsoUtils } from '../sso-logic/sso.utils';\nimport { UrlUtils } from '@tapsellorg/angular-material-library/src/lib/common';\nimport { PghSsoRedirectService } from '../sso-logic/sso-redirect.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PghSsoAuthorizedGuard {\n constructor(\n private ssoService: PghSsoService,\n private pghSsoRedirectService: PghSsoRedirectService,\n private router: Router,\n @Inject(PGH_SSO_ENRICHED_CONFIG) private readonly ssoConfig: PghSsoConfig,\n ) {}\n canActivate(\n route: ActivatedRouteSnapshot,\n _state: RouterStateSnapshot,\n ): Observable<boolean> | boolean {\n if (this.ssoConfig.isInsideSsoPanel) {\n this.pghSsoRedirectService.retrieveRedirectUrl(route.queryParams as PghLoginQueryParams);\n }\n const uuid = route.queryParamMap.get(PghSsoUtils.UUID_PARAM);\n return uuid ? this.authenticateWithUuid(uuid) : this.verifyLoginStatus();\n }\n\n private authenticateWithUuid(uuid: string): Observable<boolean> {\n return this.ssoConfig.getTokenRequest(uuid).pipe(\n switchMap(authData => this.handleAuthData(authData)),\n catchError(() => this.redirectToLogin()),\n );\n }\n\n private handleAuthData(authData: PghSsoImpersonateData): Observable<boolean> {\n if (!authData?.access_token) return this.redirectToLogin();\n this.ssoService.storeAuthData({\n accessToken: authData.access_token,\n refreshToken: authData.refresh_token,\n });\n UrlUtils.removeQueryParams(this.router, [PghSsoUtils.UUID_PARAM]);\n return this.verifyLoginStatus();\n }\n\n private verifyLoginStatus(): Observable<boolean> {\n return this.ssoService.isLoggedIn$.pipe(\n take(1),\n map(authData => {\n if (authData) {\n return true;\n }\n\n this.ssoService.redirectToLoginPageInSso();\n return false;\n }),\n );\n }\n\n private redirectToLogin(): Observable<boolean> {\n this.ssoService.redirectToLoginPageInSso();\n return of(false);\n }\n}\n","import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n Inject,\n input,\n OnInit,\n output,\n signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { Router } from '@angular/router';\nimport { PghSsoConfig } from '../sso-logic/models';\nimport { PghSsoService } from '../sso-logic/sso.service';\nimport { takeUntil } from 'rxjs/operators';\nimport { PGH_SSO_ENRICHED_CONFIG } from '../sso-logic/config';\nimport {\n withDestroy,\n PghDarkModeService,\n} from '@tapsellorg/angular-material-library/src/lib/common';\nimport { Subject } from 'rxjs';\n\n@Component({\n selector: 'pgh-user-menu',\n templateUrl: './user-menu.component.html',\n styleUrls: ['./user-menu.component.scss'],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: false,\n})\nexport class PghUserMenuComponent extends withDestroy() implements OnInit {\n userEmail = signal(this.ssoService.authData?.email);\n isAvatarNotLoaded = signal<boolean>(false);\n userName = input<string | undefined>(undefined);\n handleLogout = input<boolean | undefined>(true);\n logout = output();\n _destroyed$ = new Subject();\n\n constructor(\n @Inject(PGH_SSO_ENRICHED_CONFIG) public readonly ssoConfig: PghSsoConfig,\n private ssoService: PghSsoService,\n private router: Router,\n private cdr: ChangeDetectorRef,\n private darkModeService: PghDarkModeService,\n ) {\n super();\n }\n\n ngOnInit() {\n this.ssoService.authData$.pipe(takeUntil(this._destroyed$)).subscribe(data => {\n this.userEmail.set(data?.email);\n this.isAvatarNotLoaded.set(false);\n this.cdr.detectChanges();\n });\n }\n\n onLogout() {\n this.logout.emit();\n if (!this.handleLogout()) return;\n this.ssoService.logout();\n }\n\n toggleDarkMode() {\n this.darkModeService.toggleDarkMode();\n }\n get isDarkModeEnabled() {\n return this.darkModeService.isDarkModeEnabled;\n }\n}\n","<button class=\"py-2\" mat-button [matMenuTriggerFor]=\"menuRef\" [class.px-0]=\"!userName()\">\n <img\n *ngIf=\"userEmail()\"\n [src]=\"userEmail() | pghGravatar : 80\"\n class=\"border-radius-circle\"\n [width]=\"userName() ? 30 : 40\"\n [height]=\"userName() ? 30 : 40\"\n alt=\"\"\n (error)=\"isAvatarNotLoaded.set(true)\"\n />\n <mat-icon\n *ngIf=\"!userEmail() || isAvatarNotLoaded()\"\n svgIcon=\"account_circle\"\n class=\"square-40 text-gray\"\n ></mat-icon>\n <ng-template [ngIf]=\"userName()\">\n <span *ifMedia=\"'sm'\" class=\"ms-2\">{{ userName() }}</span>\n </ng-template>\n</button>\n\n<mat-menu #menuRef=\"matMenu\">\n <a\n [href]=\"ssoConfig.ssoPanelUrl + ssoConfig.profileRoute\"\n pghAuthorizedLink\n [exposeTokenInUrl]=\"false\"\n mat-menu-item\n class=\"text-black\"\n target=\"_blank\"\n >\n <mat-icon svgIcon=\"account_circle\"></mat-icon>\n <span *ifMedia=\"'sm'\">{{ 'SSO_USER_MENU_USER_PROFILE' | translate }}</span>\n <span *ifMedia=\"'max-xs'\">\n {{ userName() ? userName() : ('SSO_USER_MENU_USER_PROFILE' | translate) }}\n </span>\n </a>\n\n <button mat-menu-item (click)=\"toggleDarkMode()\">\n <mat-icon [svgIcon]=\"isDarkModeEnabled ? 'light_mode' : 'dark_mode'\"></mat-icon>\n <span>\n {{ (isDarkModeEnabled ? 'SSO_USER_MENU_LIGHT_MODE' : 'SSO_USER_MENU_DARK_MODE') | translate }}\n </span>\n </button>\n\n <a\n [href]=\"ssoConfig.ssoPanelUrl + ssoConfig.changePasswordRoute\"\n pghAuthorizedLink\n [exposeTokenInUrl]=\"false\"\n mat-menu-item\n class=\"text-black\"\n target=\"_blank\"\n >\n <mat-icon svgIcon=\"lock\"></mat-icon>\n <span>{{ 'SSO_USER_MENU_CHANGE_PASSWORD' | translate }}</span>\n </a>\n\n <button mat-menu-item (click)=\"onLogout()\">\n <mat-icon svgIcon=\"logout\"></mat-icon>\n <span>{{ 'SSO_USER_MENU_LOGOUT' | translate }}</span>\n </button>\n</mat-menu>\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { PghUserMenuComponent } from './user-menu.component';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatIconModule } from '@angular/material/icon';\nimport { PghSsoAuthorizedLinkModule } from '../authorized-link/authorized-link.module';\nimport { RouterModule } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\nimport { TranslateModule } from '@tapsellorg/angular-material-library/src/lib/translate';\nimport { PghMediaModule } from '@tapsellorg/angular-material-library/media';\nimport { PghGravatarModule } from '@tapsellorg/angular-material-library/src/lib/gravatar';\n\n@NgModule({\n declarations: [PghUserMenuComponent],\n imports: [\n CommonModule,\n PghGravatarModule,\n MatMenuModule,\n MatIconModule,\n PghSsoAuthorizedLinkModule,\n RouterModule,\n MatButtonModule,\n TranslateModule,\n PghMediaModule,\n ],\n exports: [PghUserMenuComponent],\n})\nexport class PghUserMenuModule {}\n","import { inject, Injectable } from '@angular/core';\nimport {\n HttpRequest,\n HttpHandler,\n HttpEvent,\n HttpInterceptor,\n HttpErrorResponse,\n} from '@angular/common/http';\nimport { catchError, EMPTY, Observable, Subject, switchMap, take, throwError } from 'rxjs';\nimport { PghSsoService } from '../sso-logic/sso.service';\nimport { PGH_SSO_ENRICHED_CONFIG } from '../sso-logic/config';\nimport { PghSsoUtils } from '../sso-logic/sso.utils';\nimport { tap } from 'rxjs/operators';\n\n@Injectable()\nexport class PghTokenInterceptor implements HttpInterceptor {\n private pghSsoService = inject(PghSsoService);\n private pghSsoConfig = inject(PGH_SSO_ENRICHED_CONFIG);\n private refreshToken$ = new Subject<void>();\n private isRefreshInProgress = false;\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return this.handleRequestWithToken(next, request);\n }\n\n private handleRequestWithToken(\n next: HttpHandler,\n request: HttpRequest<unknown>,\n ): Observable<HttpEvent<unknown>> {\n const requestWithToken = this.addAuthorizationHeader(request);\n\n return next\n .handle(requestWithToken)\n .pipe(catchError((error: HttpErrorResponse) => this.handleError(error, next, request)));\n }\n\n private handleError(\n error: HttpErrorResponse,\n next: HttpHandler,\n request: HttpRequest<unknown>,\n ): Observable<HttpEvent<unknown>> {\n const formattedError = this.formatError(error);\n\n if (!this.isAccessTokenExpired(formattedError)) {\n console.error('Error encountered:', error);\n return throwError(() => error);\n }\n\n if (this.isRefreshInProgress) {\n return this.handleRefreshInProgress(next, request);\n }\n\n return this.refreshAccessToken(next, request);\n }\n\n private handleRefreshInProgress(\n next: HttpHandler,\n request: HttpRequest<unknown>,\n ): Observable<HttpEvent<unknown>> {\n return this.refreshToken$.pipe(\n take(1),\n switchMap(() => next.handle(this.addAuthorizationHeader(request))),\n );\n }\n\n private refreshAccessToken(\n next: HttpHandler,\n request: HttpRequest<unknown>,\n ): Observable<HttpEvent<unknown>> {\n const refreshToken = this.pghSsoService.authData?.refreshToken;\n this.isRefreshInProgress = true;\n if (!refreshToken) return this.handleRefreshError();\n\n return this.pghSsoConfig.refreshTokenRequest(refreshToken).pipe(\n catchError((err: HttpErrorResponse) => {\n console.error('Refresh token failed:', err);\n return this.handleRefreshError(err);\n }),\n tap({\n next: res => {\n this.updateAuthData(res.access_token, res.refresh_token);\n this.isRefreshInProgress = false;\n this.refreshToken$.next();\n },\n error: err => {\n console.error('Error during token refresh:', err);\n throw err;\n },\n }),\n switchMap(() => next.handle(this.addAuthorizationHeader(request))),\n );\n }\n\n private handleRefreshError(error?: HttpErrorResponse): Observable<never> {\n console.error('Refresh token error:', error);\n this.isRefreshInProgress = false;\n this.pghSsoService.logout();\n\n if (!error) return EMPTY;\n\n const formattedError = this.formatError(error);\n return this.isRefreshTokenExpired(formattedError) ? EMPTY : throwError(() => error);\n }\n\n private updateAuthData(accessToken: string, refreshToken: string): void {\n this.pghSsoService.storeAuthData({ accessToken, refreshToken });\n }\n\n private addAuthorizationHeader(request: HttpRequest<unknown>): HttpRequest<unknown> {\n const isRestrictedUrl = this.pghSsoConfig.restrictedUrls.some(url =>\n request.url.startsWith(url),\n );\n\n if (!isRestrictedUrl) {\n return request;\n }\n\n const accessToken = this.pghSsoService.authData?.accessToken;\n return request.clone({\n // eslint-disable-next-line @typescript-eslint/naming-convention\n setHeaders: { Authorization: `Bearer ${accessToken}` },\n });\n }\n\n private formatError(error: HttpErrorResponse): string {\n try {\n const responseError = PghSsoUtils.parseError(error?.error);\n const { error: err, errors, code } = responseError;\n\n if (errors?.internalError?.length) {\n return errors?.internalError[0]?.code ?? 'Unknown Error Code';\n }\n\n return err?.code ?? err?.message_fa ?? err ?? code ?? 'Unknown Error';\n } catch {\n return 'Unknown Error';\n }\n }\n\n private isAccessTokenExpired(error: string): boolean {\n return this.pghSsoConfig.accessTokenExpiredError(error);\n }\n\n private isRefreshTokenExpired(error: string): boolean {\n return this.pghSsoConfig.refreshTokenExpiredError(error);\n }\n}\n","import { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Directive, HostListener, Inject, input } from '@angular/core';\nimport { PghSsoImpersonateData } from './login-as-user.model';\nimport { UrlUtils } from '@tapsellorg/angular-material-library/src/lib/common';\nimport { PGH_SSO_ENRICHED_CONFIG } from '../sso-logic/config';\nimport { PghSsoConfig } from '../sso-logic/models';\nimport { Observable, of } from 'rxjs';\nimport { PghSsoService } from '../sso-logic/sso.service';\nimport { switchMap, tap } from 'rxjs/operators';\nimport { PghSsoUtils } from '../sso-logic/sso.utils';\n\n@Directive({\n selector: '[pghLoginAsUser]',\n standalone: false,\n})\nexport class PghLoginAsUserDirective {\n url = input<string | undefined>(undefined);\n userEmail = input<string | undefined>(undefined);\n exposeTokenInUrl = input<boolean>(true);\n\n constructor(\n private http: HttpClient,\n @Inject(PGH_SSO_ENRICHED_CONFIG) private readonly ssoConfig: PghSsoConfig,\n private pghSsoService: PghSsoService,\n ) {}\n\n @HostListener('click') onClick() {\n if (!this.userEmail()) {\n throw new Error('PghLoginAsUserDirective: userEmail is not specified');\n }\n if (!this.url()) {\n throw new Error('PghLoginAsUserDirective: url is not specified');\n }\n\n const newTab = this.openLoadingTab();\n\n this.impersonateUser()\n ?.pipe(\n switchMap((tokenData: PghSsoImpersonateData) =>\n this.exposeTokenInUrl()\n ? of(\n this.directNavigation(newTab, {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n }),\n )\n : this.ssoConfig\n .getUUidRequest(tokenData)\n .pipe(\n tap(uuid => this.directNavigation(newTab, { [PghSsoUtils.UUID_PARAM]: uuid })),\n ),\n ),\n )\n .subscribe({\n error: err => {\n if (err) {\n newTab?.close();\n }\n },\n });\n }\n\n private impersonateUser(): Observable<PghSsoImpersonateData> | undefined {\n let headers;\n if (this.pghSsoService.authData) {\n headers = new HttpHeaders().set(\n 'Authorization',\n `Bearer ${this.pghSsoService.authData.accessToken}`,\n );\n }\n if (!this.userEmail) return;\n return this.http.get<PghSsoImpersonateData>(\n `${this.ssoConfig.impersonateApiUrl}?username=${encodeURIComponent(this.userEmail()!)}`,\n {\n withCredentials: true,\n headers,\n },\n );\n }\n\n private openLoadingTab(): Window | null {\n const newTab = window.open('', '_blank');\n if (newTab) {\n newTab.document.write(\n '<h2 style=\"text-align:center; margin-top:2rem\"><p>...در حال انتقال به صفحهٔ مورد نظر</p></h2>',\n );\n }\n return newTab;\n }\n\n private directNavigation(newTab: Window | null, params: Record<string, string>) {\n if (newTab) {\n newTab.location = UrlUtils.mergeParamsToUrl(this.url()!, params);\n newTab.focus();\n }\n return newTab;\n }\n}\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { PghLoginAsUserDirective } from './login-as-user.directive';\n\n@NgModule({\n declarations: [PghLoginAsUserDirective],\n imports: [CommonModule],\n exports: [PghLoginAsUserDirective],\n})\nexport class PghLoginAsUserModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.PghSsoStorageService","i2","i1","i9.PghAuthorizedLinkDirective","i1.PghSsoService","i3","i4","i5","i6","i7.PghAuthorizedLinkDirective","i8","i9","i10","i11","catchError","take","switchMap","i2.PghSsoService"],"mappings":"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