import { Config } from '../types' ; import { LoginResult } from './interface' ; import * as util from '../lib/util' ; import { AuthProvider } from './base' ; import { activateEvent , EVENTS } from '../lib/events' ; import { Adapter , RUNTIME } from '../adapters' ; import { LoginState } from './index' ; enum AllowedScopes { snsapi_base = 'snsapi_base' , snsapi_userinfo = 'snsapi_userinfo' , snsapi_login = 'snsapi_login' } const SignInPromiseMap = { } ; export class WeixinAuthProvider extends AuthProvider { config : Config ; private scope : string ; private state : string ; private appid : string ; constructor ( config , appid , scope , state ? ) { super ( config ) ; this . config = config ; this . appid = appid ; this . scope = Adapter . runtime === RUNTIME . WX_MP ? 'snsapi_base' : scope ; this . state = state || 'weixin' ; } async signInWithRedirect ( ) { return this . redirect ( ) ; } async getRedirectResult ( options ) { const code = util . getWeixinCode ( ) ; if ( ! code ) { return null ; } return this . _signInWithCode ( code , options ) ; } async getLinkRedirectResult ( options = { } ) { const { withUnionId = false } = options ; const code = util . getWeixinCode ( ) ; if ( ! code ) { return null ; } const { appid } = this ; const loginType = ( scope => { switch ( scope ) { case AllowedScopes . snsapi_login : return 'WECHAT-OPEN' ; default : return 'WECHAT-PUBLIC' ; } } ) ( this . scope ) ; const hybridMiniapp = Adapter . runtime === RUNTIME . WX_MP ? '1' : '0' ; return this . _request . send ( 'auth.linkWithWeixinCode' , { payload : { appid , loginType , code , hybridMiniapp , withUnionId } } ) ; } async signIn ( options = { } ) { const { withUnionId = false , createUser = true , syncUserInfo = false } = options ; const loginState = this . checkLocalLoginState ( ) ; if ( loginState ) { return loginState ; } if ( ! SignInPromiseMap [ this . config . env ] ) { SignInPromiseMap [ this . config . env ] = this . _signIn ( { withUnionId , createUser , syncUserInfo } ) ; } let result ; let err ; try { result = await SignInPromiseMap [ this . config . env ] ; } catch ( e ) { err = e ; } SignInPromiseMap [ this . config . env ] = null ; if ( err ) { throw err ; } return result ; } private checkLocalLoginState ( ) { const { accessTokenKey , accessTokenExpireKey } = this . _cache . keys ; let accessToken = this . _cache . getStore ( accessTokenKey ) ; let accessTokenExpire = this . _cache . getStore ( accessTokenExpireKey ) ; if ( accessToken ) { if ( accessTokenExpire && accessTokenExpire > Date . now ( ) ) { return new LoginState ( this . config . env ) ; } else { this . _cache . removeStore ( accessTokenKey ) ; this . _cache . removeStore ( accessTokenExpireKey ) ; } } } private async _signIn ( options = { withUnionId : false , createUser : true , syncUserInfo : false } ) { if ( ! AllowedScopes [ this . scope ] ) { throw new Error ( '错误的scope类型' ) ; } let code ; if ( Adapter . runtime === RUNTIME . WX_MP ) { code = await util . getMiniAppCode ( ) ; } else { code = util . getWeixinCode ( ) ; if ( ! code ) { return this . redirect ( ) ; } } return this . _signInWithCode ( code , options ) ; } private redirect ( ) { let currUrl = util . removeParam ( 'code' , location . href ) ; currUrl = util . removeParam ( 'state' , currUrl ) ; currUrl = encodeURIComponent ( currUrl ) ; let host = '//open.weixin.qq.com/connect/oauth2/authorize' ; if ( this . scope === 'snsapi_login' ) { host = '//open.weixin.qq.com/connect/qrconnect' ; } location . href = `${ host }?appid=${ this . appid }&redirect_uri=${ currUrl }&response_type=code&scope=${ this . scope }&state=${ this . state }#wechat_redirect` ; } private async _signInWithCode ( code , options ) { const { accessTokenKey , accessTokenExpireKey , refreshTokenKey } = this . _cache . keys ; const loginType = ( scope => { switch ( scope ) { case AllowedScopes . snsapi_login : return 'WECHAT-OPEN' ; default : return 'WECHAT-PUBLIC' ; } } ) ( this . scope ) ; const refreshTokenRes = await this . getRefreshTokenByWXCode ( this . appid , loginType , code , options ) ; const { refreshToken } = refreshTokenRes ; this . _cache . setStore ( refreshTokenKey , refreshToken ) ; if ( refreshTokenRes . accessToken ) { this . _cache . setStore ( accessTokenKey , refreshTokenRes . accessToken ) ; } if ( refreshTokenRes . accessTokenExpire ) { this . _cache . setStore ( accessTokenExpireKey , refreshTokenRes . accessTokenExpire + Date . now ( ) ) ; } activateEvent ( EVENTS . LOGIN_STATE_CHANGED ) ; activateEvent ( EVENTS . LOGIN_TYPE_CHANGED , { env : this . config . env , loginType : loginType , persistence : this . config . persistence } ) ; await this . refreshUserInfo ( ) ; return new LoginState ( this . config . env ) ; } private async getRefreshTokenByWXCode ( appid , loginType , code , options = { } ) { const { withUnionId = false , createUser = true } = options ; const syncUserInfo = this . scope === 'snsapi_base' ? false : options . syncUserInfo || false ; const action = 'auth.signIn' ; const hybridMiniapp = Adapter . runtime === RUNTIME . WX_MP ? '1' : '0' ; return this . _request . send ( action , { appid , loginType , loginCredential : code , syncUserInfo , hybridMiniapp , withUnionId , createUser } ) . then ( res => { if ( res . code ) { throw new Error ( `[tcb-js-sdk] 微信登录失败: ${ res . code }` ) ; } if ( res . refresh_token ) { return { refreshToken : res . refresh_token , accessToken : res . access_token , accessTokenExpire : res . access_token_expire } ; } else { throw new Error ( `[tcb-js-sdk] getJwt未返回refreshToken` ) ; } } ) ; } }