Source: sdk-file/src/qq/auth.js

const constants = require('core-module/constants')
const HError = require('core-module/HError')
const storage = require('core-module/storage')
const storageAsync = require('core-module/storageAsync')
const utils = require('core-module/utils')
const commonAuth = require('core-module/auth')

module.exports = BaaS => {
  const API = BaaS._config.API

  const getLoginCode = () => {
    return new Promise((resolve, reject) => {
      qq.login({
        success: res => {
          resolve(res.code)
        },
        fail: () => {
          BaaS.request.qqRequestFail(reject)
        },
      })
    })
  }

  // 获取登录凭证 code, 进而换取用户登录态信息
  const auth = ({createUser = true} = {}) => {
    return new Promise((resolve, reject) => {
      getLoginCode().then(code => {
        sessionInit({code, createUser}, resolve, reject)
      }, reject)
    })
  }

  // code 换取 session_key,生成并获取 3rd_session 即 token
  const sessionInit = ({code, createUser}, resolve, reject) => {
    return BaaS.request({
      url: API.QQ.SILENT_LOGIN,
      method: 'POST',
      data: {
        create_user: createUser,
        code: code
      }
    })
      .then(utils.validateStatusCode)
      .then(utils.flatAuthResponse)
      .then(res => {
        BaaS._polyfill.handleLoginSuccess(res)
        resolve(res)
      }, reject)
  }

  const silentLogin = utils.rateLimit(function (...args) {
    return Promise.all([
      storageAsync.get(constants.STORAGE_KEY.AUTH_TOKEN),
      utils.isSessionExpired(),
    ]).then(res => {
      const [token, isExpired] = res
      if (token && !isExpired) {
        return Promise.resolve()
      }
      return auth(...args)
    })
  })

  const getSensitiveData = (data) => {
    return BaaS.request({
      url: API.QQ.AUTHENTICATE,
      method: 'POST',
      data,
    })
      .then(utils.validateStatusCode)
      .then(utils.flatAuthResponse)
  }

  const getUserInfo = ({lang} = {}) => {
    return new Promise((resolve, reject) => {
      qq.getUserInfo({
        lang,
        success: resolve, fail: reject
      })
    })
  }

  // 提供给开发者在 button (open-type="getUserInfo") 的回调中调用,对加密数据进行解密,同时将 userinfo 存入 storage 中
  const handleUserInfo = res => {
    if (!res || !res.detail) {
      throw new HError(603)
    }

    let detail = res.detail
    let createUser = !!res.createUser
    let syncUserProfile = res.syncUserProfile

    // 用户拒绝授权,仅返回 uid, openid
    if (!detail.userInfo) {
      return Promise.reject(Object.assign(new HError(603), {
        id: storage.get(constants.STORAGE_KEY.UID),
        openid: storage.get(constants.STORAGE_KEY.OPENID),
      }))
    }

    return getLoginCode().then(code => {
      return getUserInfo({lang: detail.userInfo.language}).then(detail => {
        let payload = {
          code,
          create_user: createUser,
          rawData: detail.rawData,
          signature: detail.signature,
          encryptedData: detail.encryptedData,
          iv: detail.iv,
          update_userprofile: utils.getUpdateUserProfileParam(syncUserProfile),
        }
        return getSensitiveData(payload)
      })
    }).then(res => {
      let userInfo = detail.userInfo
      userInfo.id = res.data.user_id
      userInfo.openid = res.data.openid
      userInfo.unionid = res.data.unionid
      BaaS._polyfill.handleLoginSuccess(res, false, userInfo)
      return res
    })
  }

  const linkQQ = (res, {
    syncUserProfile = constants.UPDATE_USERPROFILE_VALUE.SETNX,
  } = {}) => {
    let refreshUserInfo = false
    if (res && res.detail && res.detail.userInfo) {
      refreshUserInfo = true
    }

    return getLoginCode().then(code => {
      // 如果用户传递了授权信息,则重新获取一次 userInfo, 避免因为重新获取 code 导致 session 失效而解密失败
      let getUserInfoPromise = refreshUserInfo
        ? getUserInfo({lang: res.detail.userInfo.language})
        : Promise.resolve(null)

      return getUserInfoPromise.then(res => {
        let payload = res ? {
          rawData: res.rawData,
          signature: res.signature,
          encryptedData: res.encryptedData,
          iv: res.iv,
          update_userprofile: utils.getUpdateUserProfileParam(syncUserProfile),
          code
        } : {code}

        return BaaS._baasRequest({
          method: 'POST',
          url: API.QQ.USER_ASSOCIATE,
          data: payload,
        })
      })
    })
  }

  /**
   * QQ 登录
   * @function
   * @since v2.2.0
   * @memberof BaaS.auth
   * @param {BaaS.AuthData|null} [authData] 用户信息,值为 null 时是静默登录
   * @param {BaaS.LoginOptions} [options] 其他选项
   * @return {Promise<BaaS.CurrentUser>}
   */
  const loginWithQQ = (authData, {
    createUser = true,
    syncUserProfile = constants.UPDATE_USERPROFILE_VALUE.SETNX,
  } = {}) => {
    let loginPromise = null
    if (authData && authData.detail) {
      // handleUserInfo 流程
      loginPromise = handleUserInfo(Object.assign(authData, {createUser, syncUserProfile}))
    } else {
      // 静默登录流程
      loginPromise = silentLogin({createUser})
    }

    return loginPromise.then((res) => {
      if (!res) return commonAuth.getCurrentUser()
      return commonAuth._initCurrentUser(res.data.user_info, res.data.expired_at)
    })
  }

  Object.assign(BaaS.auth, {
    silentLogin,
    loginWithQQ: utils.rateLimit(loginWithQQ),
    linkQQ: utils.rateLimit(linkQQ),
  })
}