Auth/oauth.js

import { buildQueryString, extend } from '../util'
import { getAppKey, KAKAO_AGENT, processRules, UA, URL } from '../common'
import eventObserver from '../eventObserver'
import Poller from '../poller'

import rules from './rules'
import * as authCommon from './common'
import { checkAuthorize } from './ajax'

const poller = new Poller(1000, 600); // 10 min timeout

/**
 * 사용자가 앱에 로그인할 수 있도록 인가 코드를 요청하는 함수입니다. 인가 코드를 받을 수 있는 서버 개발이 필요합니다.
 * @function authorize
 * @param {Object} settings 인가 코드 요청과 관련된 설정을 key/value로 전달합니다.
 * @param {String} [settings.redirectUri] 인가 코드를 받을 URI
 * @param {String} [settings.state] 인가 코드 요청과 응답 과정에서 유지할 수 있는 파라미터
 * @param {String} [settings.scope] 추가 동의 받을 항목의 키 ex) "account_email,gender"
 * @param {Boolean} [settings.throughTalk=true] 간편 로그인 사용 여부
 * @param {String} [settings.prompts] 인가 코드 요청 시 추가 상호작용을 요청하고자 할 때 전달하는 파라미터 ex) "login": 다른 계정으로 로그인
 * @see {@link https://developers.kakao.com/tool/demo/login/login|데모 보러가기}
 * @memberof Kakao.Auth
 */
export function authorize(settings) {
  settings = processRules(settings, rules.authorize, 'Auth.authorize')

  if (settings.autoLogin && !/KAKAOTALK/i.test(UA.ua)) {
    handleResponse(settings, {
      error: 'auto_login',
      error_description: 'NOT_SUPPORTED_BROWSER',
    })
    return false
  }

  const authTranId = getTranId()
  const baseAuthParams = extend({},
    authCommon.makeAuthParams(settings),
    authCommon.makeAuthExtraParams(settings),
    {
      redirect_uri: settings.redirectUri || URL.redirectUri,
      response_type: 'code',
      auth_tran_id: authTranId,
    },
  )

  const webAuthParams = extend({},
    baseAuthParams,
    {
      ka: KAKAO_AGENT,
      is_popup: settings.isPopup,
    },
  )

  const isEasyLogin = isSupportEasyLogin(settings)
  const isSupportSyncplugin = isTalkChannelHome(settings)

  const webAuthUrl = authCommon.makeAuthUrl(webAuthParams)
  const loginUrl = isEasyLogin ? makeEasyLoginUrl(settings, baseAuthParams, webAuthUrl) : webAuthUrl

  let popup = null
  if (isSupportSyncplugin) {
    executeSyncpluginScheme(baseAuthParams)
  } else if (settings.isPopup) {
    popup = authCommon.openLoginPopup(loginUrl)
  } else {
    location.href = loginUrl
  }

  if (isEasyLogin || isSupportSyncplugin || settings.isPopup) {
    poller.start(
      () => {
        const params = {
          client_id: getAppKey(),
          auth_tran_id: authTranId,
          ka: KAKAO_AGENT,
        }

        checkAuthorize(
          `${URL.authDomain}/apiweb/code.json?${buildQueryString(params)}`,
          httpResp => {
            const isValidResp = onResponse(settings, httpResp)
            if (isValidResp) {
              poller.stop()
              popup && popup.close && popup.close()
            }

            if(!isEasyLogin && popup && popup.closed) {
              poller.stop()
            }
          },
        )
      },
      () => {
        handleResponse(settings, {
          error: 'timeout',
          error_description: 'LOGIN_TIMEOUT',
        })
      },
    )
  }

  eventObserver.dispatch('LOGIN_START')
}

function isSupportEasyLogin(settings) {
  const isNotInAppBrowser = (UA.os.ios || UA.os.android) ? !/KAKAOTALK/i.test(UA.ua) : false
  const isNotAccountLogin = !settings.reauthenticate && settings.prompts !== 'login'

  return isNotInAppBrowser && isNotAccountLogin && settings.throughTalk && !settings.autoLogin
}

function getTranId() {
  const tranId = Math.random().toString(36).slice(2) + getAppKey() + Date.now().toString(36)
  return tranId.slice(0, 60)
}

function onResponse(settings, httpResp) {
  if (httpResp.status === 200 && httpResp.response) {
    const resp = JSON.parse(httpResp.response)

    if (resp.status === 'ok' && resp.code) {
      handleResponse(settings, {
        code: resp.code,
      })
      return true
    } else if (resp.status === 'error' && (resp.error_code === '500' || resp.error_code === '600' || resp.error_code === '700')) {
      handleResponse(settings, {
        error: resp.error,
        error_description: resp.error_description,
      })

      if (resp.error_code === '700') {
        location.href = `${URL.authDomain}/error/network`
      }

      return true
    }
  }

  return false
}

function handleResponse(settings, respObj) {
  if (settings.state) {
    respObj.state = settings.state
  }

  if (settings.redirectUri) {
    location.href = `${settings.redirectUri}?${buildQueryString(respObj)}`
  } else {
    authCommon.runAuthCallback(settings, respObj)
  }
}

function makeEasyLoginUrl(settings, baseAuthParams, fallbackUrl) {
  const easyLoginAuthParams = extend({},
    baseAuthParams,
    {
      is_popup: true,
    },
  )

  // TODO: ./kakaotalk method를 활용하도록 리팩토링
  const getAndroidLoginIntent = () => {
    const intent = [
      'intent:#Intent',
      'action=com.kakao.talk.intent.action.CAPRI_LOGGED_IN_ACTIVITY',
      'launchFlags=0x08880000',
      `S.com.kakao.sdk.talk.appKey=${getAppKey()}`,
      `S.com.kakao.sdk.talk.redirectUri=${easyLoginAuthParams.redirect_uri}`,
      `S.com.kakao.sdk.talk.kaHeader=${KAKAO_AGENT}`,
      `S.com.kakao.sdk.talk.extraparams=${encodeURIComponent(JSON.stringify(easyLoginAuthParams))}`,
    ]

    if (settings.state) {
      intent.push(`S.com.kakao.sdk.talk.state=${settings.state}`)
    }

    return intent.concat([
      `S.browser_fallback_url=${encodeURIComponent(fallbackUrl)}`,
      'end;'
    ]).join(';')
  }

  const getIosLoginUniversalLink = () => {
    const iosLoginUrl = authCommon.makeAuthUrl(easyLoginAuthParams)
    const iosFallbackUrl = settings.isPopup ? iosLoginUrl : fallbackUrl

    const iosEasyLoginUrl = `${iosLoginUrl}&ka=${encodeURIComponent(KAKAO_AGENT)}`
    const talkWebviewUrl = `${URL.talkInappScheme}?url=${encodeURIComponent(iosEasyLoginUrl)}`

    return `${URL.universalKakaoLink}${encodeURIComponent(talkWebviewUrl)}&web=${encodeURIComponent(iosFallbackUrl)}`
  }

  return UA.os.android ? getAndroidLoginIntent() : getIosLoginUniversalLink()
}

function isTalkChannelHome(settings) {
  return settings.throughSyncplugin && /KAKAOTALK/i.test(UA.ua) && /ch-home/i.test(UA.ua)
}

function executeSyncpluginScheme(baseAuthParams) {
  const bizpluginParams = extend({},
    baseAuthParams,
    {
      ka: KAKAO_AGENT,
      is_popup: true,
      approval_window_type: 'v4_bizplugin',
    },
  )

  const query = encodeURIComponent(buildQueryString(bizpluginParams))
  location.href = `${URL.talkSyncpluginScheme}&query=${query}`
}