<commit_before>import { randomBytes } from 'crypto'
import { parse as parseQueryString } from 'querystring'
import { parse as parseUrl } from 'url'
import * as Debug from 'debug'

import { app } from 'electron'
import { enableLiveReload } from 'electron-compile'

import {
  getAccessToken,
  refreshAccess,
  verifyAccess,
  getSavedCode,
  saveCode
} from '../lib/monzo/auth'
import WindowManager from './window-manager'

const debug = Debug('app:app')
const mainWindow = new WindowManager()

enableLiveReload()

debug(`starting`, app.getName(), 'version', app.getVersion())

export interface IAppInfo {
  client_id: string
  client_secret: string
  redirect_uri: string
  response_type: string
  state: string
}

const getAppInfo = (() => {
  const state = new Promise<string>((resolve, reject) => {
    randomBytes(128, (err, buf) => {
      if (err) reject(err)
      resolve(buf.toString('hex'))
    })
  })

  return async (): Promise<IAppInfo> => {
    return {
      client_id: await getSavedCode('client_id'),
      client_secret: await getSavedCode('client_secret'),
      redirect_uri: 'monux://auth/',
      response_type: 'code',
      state: await state
    }
  }
})()

app.on('ready', async () => {
  debug('ready event')

  try {
    const appInfo = await getAppInfo()

    try {
      const accessToken = await getSavedCode('access_token')

      try {
        const access = await verifyAccess(accessToken)

        if (access) {
          mainWindow.goToMonux()
        } else {
          const refreshToken = await getSavedCode('refresh_token')

          const {
            accessToken: newAccessToken,
            refreshToken: newRefreshToken
          } = await refreshAccess(appInfo, refreshToken)

          if (await verifyAccess(newAccessToken)) {
            await saveCode('access_token', newAccessToken)
            await saveCode('refresh_token', newRefreshToken)

            mainWindow.goToMonux()
          } else {
            console.error('Invalid refresh token')
            throw new Error('Invalid refresh token')
          }
        }
      } catch (err) {
        console.warn(err.name)
        if (err.name === 'RequestError') mainWindow.goToMonux()
        else throw new Error(err)
      }
    } catch (err) {
      mainWindow.goToAuthRequest(appInfo)
    }
  } catch (err) {
    mainWindow.goToClientInfo()
  }
})

app.on('open-url', async (_, forwardedUrl) => {
  debug('open-url event')

  const appInfo = await getAppInfo()

  const query = parseUrl(forwardedUrl).query
  const authResponse = parseQueryString(query)

  if (authResponse.state !== appInfo.state) {
    console.error('Auth state mismatch')
    throw new Error('Auth state mismatch')
  }

  const authCode = authResponse.code
  debug('authcode =>', authCode)

  try {
    const { accessToken, refreshToken } = await getAccessToken(
      appInfo,
      authCode
    )

    debug('token =>', accessToken)
    if (await verifyAccess(accessToken)) {
      await saveCode('access_token', accessToken)
      await saveCode('refresh_token', refreshToken)

      mainWindow.goToMonux()
    } else {
      console.error('Invalid access token')
      throw new Error('Invalid access token')
    }
  } catch (err) {
    console.error(err)
    throw new Error(err)
  }<commit_msg>graceful fallback when refresh token is not sent<commit_after>import { randomBytes } from 'crypto'
import { parse as parseQueryString } from 'querystring'
import { parse as parseUrl } from 'url'
import * as Debug from 'debug'

import { app } from 'electron'
import { enableLiveReload } from 'electron-compile'

import {
  getAccessToken,
  refreshAccess,
  verifyAccess,
  getSavedCode,
  saveCode
} from '../lib/monzo/auth'
import WindowManager from './window-manager'

const debug = Debug('app:app')
const mainWindow = new WindowManager()

enableLiveReload()

debug(`starting`, app.getName(), 'version', app.getVersion())

export interface IAppInfo {
  client_id: string
  client_secret: string
  redirect_uri: string
  response_type: string
  state: string
}

const getAppInfo = (() => {
  const state = new Promise<string>((resolve, reject) => {
    randomBytes(128, (err, buf) => {
      if (err) reject(err)
      resolve(buf.toString('hex'))
    })
  })

  return async (): Promise<IAppInfo> => {
    return {
      client_id: await getSavedCode('client_id'),
      client_secret: await getSavedCode('client_secret'),
      redirect_uri: 'monux://auth/',
      response_type: 'code',
      state: await state
    }
  }
})()

app.on('ready', async () => {
  debug('ready event')

  try {
    const appInfo = await getAppInfo()

    try {
      const accessToken = await getSavedCode('access_token')

      try {
        const access = await verifyAccess(accessToken)

        if (access) {
          mainWindow.goToMonux()
        } else {
          try {
            const refreshToken = await getSavedCode('refresh_token')

            const {
              accessToken: newAccessToken,
              refreshToken: newRefreshToken
            } = await refreshAccess(appInfo, refreshToken)

            if (await verifyAccess(newAccessToken)) {
              await saveCode('access_token', newAccessToken)
              await saveCode('refresh_token', newRefreshToken)

              mainWindow.goToMonux()
            } else {
              console.error('Invalid refresh token')
              throw new Error('Invalid refresh token')
            }
          } catch (err) {
            debug('no refresh token found')
            mainWindow.goToAuthRequest(appInfo)
          }
        }
      } catch (err) {
        console.warn(err.name)
        if (err.name === 'RequestError') mainWindow.goToMonux()
        else throw new Error(err)
      }
    } catch (err) {
      debug('no access token found')
      mainWindow.goToAuthRequest(appInfo)
    }
  } catch (err) {
    debug('no client info found')
    mainWindow.goToClientInfo()
  }
})

app.on('open-url', async (_, forwardedUrl) => {
  debug('open-url event')

  const appInfo = await getAppInfo()

  const query = parseUrl(forwardedUrl).query
  const authResponse = parseQueryString(query)

  if (authResponse.state !== appInfo.state) {
    console.error('Auth state mismatch')
    throw new Error('Auth state mismatch')
  }

  const authCode = authResponse.code
  debug('authcode =>', authCode)

  try {
    const { accessToken, refreshToken } = await getAccessToken(
      appInfo,
      authCode
    )

    debug('token =>', accessToken)
    if (await verifyAccess(accessToken)) {
      await saveCode('access_token', accessToken)

      if (refreshToken) await saveCode('refresh_token', refreshToken)
      else debug('no refresh token sent')

      mainWindow.goToMonux()
    } else {
      console.error('Invalid access token')
      throw new Error('Invalid access token')
    }
  } catch (err) {
    console.error(err)
    throw new Error(err)
  }
{'fraction_non_alphanumeric': 0.11622521348965117, 'fraction_numerical': 0.0008684324793747286, 'mean_word_length': 2.802971931755641, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '555931', 'n_tokens_mistral': 2050, 'n_tokens_neox': 2010, 'n_words': 498}