{"version":3,"sources":["lib/authMgr/authControl.ts"],"names":[],"mappings":"AACA,OAAO,EAA4B,MAAM,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,WAAW,EAAc,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAGhG,OAAO,EAA+C,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEpF,UAAU,gBAAgB;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,KAAK;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;CACpB;AAID,qBAAa,UAAU;IACrB;;SAEK;IACL,OAAc,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAkCtD;IAGK,KAAK,EAAE,KAAK,CAAQ;gBAEf,KAAK,EAAE,KAAK;IAIxB;;;SAGK;IACQ,aAAa,IAAI,OAAO,CAAC,QAAQ,CAAC;IAa/C;;;SAGK;IACQ,cAAc,IAAI,OAAO,CAAC,QAAQ,CAAC;IAahD;;;;;SAKK;IACE,eAAe,EAAE,aAAa,CAqCnC;CACH;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,WAAW;IAC5C,iBAAiB,IAAI,IAAI;IAIzB,oBAAoB,IAAI,IAAI;CAGpC;AAgBD,eAAe,oBAAoB,CAAC","file":"../../../lib/authMgr/authControl.d.ts","sourcesContent":["import AppContext, { getTools, ToolBox } from '../../common/appContext/appContext.js';\nimport { aliasName as loggerAlias, Logger } from '../../common/appContext/logging.js';\nimport { SharedState, StateEvent, StateListener } from '../../common/appContext/sharedState.js';\nimport { once } from '../../common/mutexHelper.js';\nimport { HistoryState, stateKey as historyStateKey } from '../appContext/historyHelper.js';\nimport { anonymousUserInfo, stateKey as authStateKey, UserInfo } from './authUi.js';\n\ninterface ControllerConfig {\n  userInfoEndpoint: string;\n  loginRedirect: string;\n  logoutRedirect: string;\n}\n\ninterface Tools {\n  config: ControllerConfig;\n  log: Logger;\n  state: SharedState;\n}\n\nconst configKey = 'littleware/lib/authMgr/controller';\n\nexport class Controller {\n  /**\n     * Controller factory enforces singleton\n     */\n  public static startController: () => Promise<Controller> = once(\n    async () => new Promise(\n      (resolve) => {\n        AppContext.get().then(\n          (cx) => cx.onStart(\n            {\n              log: loggerAlias,\n              state: SharedState.providerName,\n              config: `config/${configKey}`,\n            },\n            async (toolBox: ToolBox) => {\n              const tools = await getTools(toolBox).then(\n                (toolsFromBox) => ({\n                  ...toolsFromBox,\n                  config: { ...toolsFromBox.config.defaults, ...toolsFromBox.config.overrides },\n                }) as Tools,\n              );\n              const controller = new Controller(tools);\n              controller.updateUserInfo().then(\n                () => {\n                  // check in every 5 minutes\n                  setInterval(\n                    async () => {\n                      controller.updateUserInfo();\n                    },\n                    300000,\n                  );\n                },\n              );\n              tools.state.addListener(historyStateKey, controller.navEventHandler);\n              resolve(controller);\n            }));\n      },\n    ) as Promise<Controller>,\n  );\n\n  // initialized below - before web component registered\n  public tools: Tools = null;\n\n  constructor(tools: Tools) {\n    this.tools = tools;\n  }\n\n  /**\n     * Fetch UserInfo from the config.userInfoEndpoint,\n     * return anonymousUserInfo on failure to fetch\n     */\n  public async fetchUserInfo(): Promise<UserInfo> {\n    const info = await fetch(\n      this.tools.config.userInfoEndpoint,\n      { mode: 'cors', credentials: 'include' },\n    ).then(\n      (res) => res.json(),\n    ) as UserInfo;\n    if (!info.email) {\n      return anonymousUserInfo;\n    }\n    return info;\n  }\n\n  /**\n     * Fetch the latest user info, and update the shared state\n     * if necessary\n     */\n  public async updateUserInfo(): Promise<UserInfo> {\n    const result = await this.tools.state.changeState(authStateKey,\n      async (oldInfo: UserInfo) => {\n        const newInfo = await this.fetchUserInfo();\n        if (newInfo.email !== oldInfo.email) {\n          return newInfo;\n        }\n        // no state change\n        return null;\n      }) as UserInfo;\n    return result;\n  }\n\n  /**\n     * Handle the login/logout statemachine:\n     * #authmgr/login, #authmgr/logout, #authmgr/loginresult, #authmgr/userinfo\n     *\n     * @param ev\n     */\n  public navEventHandler: StateListener = (ev: StateEvent) => {\n    const hashPath = (ev.data.new as HistoryState).hashPath || '';\n    let redirect = ''; // no redirect by default\n    let newUrl = '';\n    if (hashPath.endsWith('authmgr/login')) {\n      let redirectHash = (ev.data.old as HistoryState).hashPath;\n      if (redirectHash.endsWith('/login') || redirectHash.endsWith('/logout')) {\n        redirectHash = '';\n      }\n      newUrl = this.tools.config.loginRedirect;\n      redirect = `${globalThis.location.origin}${globalThis.location.pathname}?backto=${encodeURIComponent(redirectHash)}#authmgr/loginresult`;\n    } else if (hashPath.endsWith('authmgr/logout')) {\n      newUrl = this.tools.config.logoutRedirect;\n      redirect = `${globalThis.location.origin}${globalThis.location.pathname}`;\n    }\n    if (newUrl && redirect) {\n      newUrl += (newUrl.indexOf('?') > 0) ? '&' : '?';\n      newUrl += `redirect_uri=${encodeURIComponent(redirect)}`;\n      globalThis.location.replace(newUrl);\n      return;\n    }\n    if (hashPath.endsWith('authmgr/loginresult')) {\n      const params = (new URL(globalThis.location.href)).searchParams;\n      const backto = params.get('backto') || '';\n      params.delete('backto');\n      params.delete('state');\n      params.delete('backto');\n      globalThis.history.replaceState({}, '', `?${params.toString()}#${backto}`);\n    } else if (hashPath.endsWith('authmgr/userinfo')) {\n      if ((ev.data.old as HistoryState).hashPath) {\n        // TODO - implement userinfo endpoint\n        this.tools.log.info('authmgr/userinfo not yet implemented');\n        globalThis.history.go(-1);\n      } else {\n        globalThis.location.hash = '';\n      }\n    }\n  };\n}\n\n/**\n * Controller web component - launches singleton controller\n * when placed on page.\n */\nexport class LittleAuthController extends HTMLElement {\n  public connectedCallback(): void {\n    Controller.startController();\n  }\n\n  public disconnectedCallback(): void {\n    // console.log( \"Disconnected!\" );\n  }\n}\n\nAppContext.get().then(\n  (cx) => {\n    const apiDomain = 'api.frickjack.com';\n    cx.putDefaultConfig(configKey,\n      {\n        loginRedirect: `https://${apiDomain}/authn/login`,\n        logoutRedirect: `https://${apiDomain}/authn/logout`,\n        userInfoEndpoint: `https://${apiDomain}/authn/user`,\n      } as ControllerConfig);\n  },\n);\n\nwindow.customElements.define('lw-auth-control', LittleAuthController);\n\nexport default LittleAuthController;\n"]}