import UUID from "pure-uuid" ; import type { Action } from "@ngrx/store" ; import { Actions } from "@ngrx/effects" ; import { EMPTY , concat , lastValueFrom , timer } from "rxjs" ; import { Injectable , NgZone } from "@angular/core" ; import { Store , select } from "@ngrx/store" ; import { URL } from "@cliqz/url-parser" ; import { filter , first , mergeMap , take , takeUntil } from "rxjs/operators" ; import { ACCOUNTS_ACTIONS , NAVIGATION_ACTIONS } from "src/web/browser-window/app/store/actions" ; import { AccountConfig } from "src/shared/model/account" ; import { FIRE_SYNCING_ITERATION$ , SETTINGS_OUTLET , SETTINGS_PATH } from "src/web/browser-window/app/app.constants" ; import { IpcMainServiceScan } from "src/shared/api/main-process" ; import { OptionsSelectors } from "src/web/browser-window/app/store/selectors" ; import { PROVIDER_REPO_MAP } from "src/shared/proton-apps-constants" ; import { ProtonClientSession } from "src/shared/model/proton" ; import { State } from "src/web/browser-window/app/store/reducers/root" ; import { WEB_CLIENTS_BLANK_HTML_FILE_NAME , WEB_VIEW_SESSION_STORAGE_KEY_SKIP_LOGIN_DELAYS , } from "src/shared/constants" ; import { WebAccount } from "src/web/browser-window/app/model" ; import { curryFunctionMembers , parseUrlOriginWithNullishCheck } from "src/shared/util" ; import { ofType } from "src/shared/ngrx-util-of-type" ; @ Injectable ( ) export class CoreService { constructor ( private store , private zone , private readonly actions$ ) { } parseEntryUrl ( accountConfig , repoType ) { const entryApiUrl = accountConfig . entryUrl ; if ( ! entryApiUrl || ! entryApiUrl . startsWith ( "https://" ) ) { throw new Error ( `Invalid "entryApiUrl" value: "${ entryApiUrl }"` ) ; } const bundle = __METADATA__ . electronLocations . webClients . filter ( ( webClient ) => webClient . entryApiUrl === entryApiUrl ) . pop ( ) ; if ( ! bundle ) { throw new Error ( `Invalid "entryUrl" value: "${ JSON . stringify ( bundle ) }"` ) ; } const { basePath } = PROVIDER_REPO_MAP [ repoType ] ; const entryUrl = `${ bundle . entryUrl }${ basePath ? "/" + basePath : "" }` ; return { entryUrl , entryApiUrl , } ; } async applyProtonClientSessionAndNavigate ( accountConfig , repoType , webViewDomReady$ , setWebViewSrc , logger_ , savedSessionData ? ) { const logger = curryFunctionMembers ( logger_ , __filename , nameof ( CoreService . prototype . applyProtonClientSessionAndNavigate ) ) ; const timeouts = await lastValueFrom ( this . store . pipe ( select ( OptionsSelectors . CONFIG . timeouts ) , first ( ) ) ) ; const { webViewBlankDOMLoaded : loaderIdTimeoutMs } = timeouts ; const loaderId = new UUID ( 4 ) . format ( ) ; const loaderIdParam = "loader-id" ; const loaderSrcOrigin = parseUrlOriginWithNullishCheck ( this . parseEntryUrl ( accountConfig , repoType ) . entryUrl ) ; const loaderSrc = `${ loaderSrcOrigin }/${ WEB_CLIENTS_BLANK_HTML_FILE_NAME }?${ loaderIdParam }=${ loaderId }` ; let webView ; logger . verbose ( "setTimeout" ) ; setTimeout ( ( ) => { logger . verbose ( "this.zone.run" ) ; this . zone . run ( ( ) => { logger . verbose ( "setWebViewSrc" ) ; setWebViewSrc ( loaderSrc ) ; } ) ; } ) ; try { logger . verbose ( "webViewDomReady$" ) ; webView = await lastValueFrom < Exclude < typeof webView , undefined > > ( webViewDomReady$ . pipe ( filter ( ( { src } ) => { const result = Boolean ( src ) && new URL ( src ) . searchParams . get ( loaderIdParam ) === loaderId ; logger . verbose ( "webViewDomReady$ filter" , JSON . stringify ( { src , result } ) ) ; return result ; } ) , takeUntil ( timer ( loaderIdTimeoutMs ) ) , first ( ) ) ) ; } catch ( error ) { const message = `Failed to load "${ loaderSrc }" page in ${ loaderIdTimeoutMs }ms` ; logger . error ( message , error ) ; throw new Error ( message ) ; } const javaScriptCode = ( ( ) => { const generateSessionStoragePatchingCode = ( patch ) : string => { return `(() => {
                    const sessionStorageStr = ${ JSON . stringify ( JSON . stringify ( patch ) ) };
                    const sessionStorageParsed = JSON.parse(sessionStorageStr);
                    for (const [key, value] of Object.entries(sessionStorageParsed)) {
                        window.sessionStorage.setItem(key, value);
                    }
                })();` ; } ; const finalCodePart = `(() => {
                window.sessionStorage.setItem(${ JSON . stringify ( WEB_VIEW_SESSION_STORAGE_KEY_SKIP_LOGIN_DELAYS ) }, 1);
                window.location.assign("./${ PROVIDER_REPO_MAP [ repoType ] . basePath }")
            })()` ; const prependCodeParts = [ ] ; if ( savedSessionData ?. clientSession ) { prependCodeParts . push ( ... [ generateSessionStoragePatchingCode ( savedSessionData ?. clientSession . sessionStorage ) , `(() => {
                        const windowNameStr = ${ JSON . stringify ( JSON . stringify ( savedSessionData ?. clientSession . windowName ) ) };
                        window.name = windowNameStr;
                    })();` , ] ) ; } if ( savedSessionData ?. sessionStoragePatch ) { prependCodeParts . push ( generateSessionStoragePatchingCode ( savedSessionData ?. sessionStoragePatch ) ) ; } if ( prependCodeParts . length ) { return `
                    ${ prependCodeParts . join ( "\n\r" ) };
                    ${ finalCodePart }
                ` ; } return `
                window.name = "";
                window.sessionStorage.clear();
                ${ finalCodePart }
            ` ; } ) ( ) ; try { logger . verbose ( "executeJavaScript" ) ; await webView . executeJavaScript ( javaScriptCode ) ; } catch ( error ) { const baseMessage = `Failed to set shared session object on "${ loaderSrc }" page ("executeJavaScript")` ; if ( BUILD_ENVIRONMENT === "development" ) { console . log ( baseMessage , error ) ; } throw new Error ( baseMessage ) ; } } openSettingsView ( ) { this . store . dispatch ( NAVIGATION_ACTIONS . Go ( { path : [ { outlets : { [ SETTINGS_OUTLET ] : SETTINGS_PATH } } ] , } ) ) ; } logOut ( skipKeytarProcessing ? ) { this . store . dispatch ( NAVIGATION_ACTIONS . Logout ( { skipKeytarProcessing } ) ) ; } dispatch ( action ) { this . zone . run ( ( ) => { this . store . dispatch ( action ) ; } ) ; } fireSyncingIteration ( { login } ) { setTimeout ( ( ) => FIRE_SYNCING_ITERATION$ . next ( { login } ) ) ; return concat ( this . actions$ . pipe ( ofType ( ACCOUNTS_ACTIONS . PatchProgress ) , filter ( ( { payload } ) => payload . login === login && Boolean ( payload . patch . syncing ) ) , take ( 1 ) ) , this . actions$ . pipe ( ofType ( ACCOUNTS_ACTIONS . Synced ) , filter ( ( { payload } ) => payload . pk . login === login ) , take ( 1 ) ) ) . pipe ( mergeMap ( ( ) => EMPTY ) ) ; } }