import AsyncStorage from "@react-native-community/async-storage" ; import { AuthContainer , BaseAPIClient , Container , ContainerOptions , GlobalJSONContainerStorage , SSOLoginOptions , StorageDriver , User , _PresignUploadRequest , decodeError , OIDCContainer , AuthorizeOptions , PromoteOptions , } from "@skygear/core" ; import { generateCodeVerifier , computeCodeChallenge } from "./pkce" ; import { openURL , openAuthorizeURL , signInWithApple , getCredentialStateForUserID , addAppleIDCredentialRevokedListener , } from "./nativemodule" ; import { extractResultFromURL , getCallbackURLScheme } from "./url" ; import { getAnonymousJWK , signAnonymousJWT } from "./jwt" ; export * from "@skygear/core" ; export { addAppleIDCredentialRevokedListener , getCredentialStateForUserID } ; const globalFetch = fetch ; public export class ReactNativeAPIClient extends BaseAPIClient { fetchFunction = globalFetch ; requestClass = Request ; } public export class ReactNativeAsyncStorageStorageDriver implements StorageDriver { get ( key ) { return AsyncStorage . getItem ( key ) ; } set ( key , value ) { return AsyncStorage . setItem ( key , value ) ; } del ( key ) { return AsyncStorage . removeItem ( key ) ; } } async function uploadForm ( url , req , uri , onUploadProgress ? ) { const form = new FormData ( ) ; if ( req . prefix != null ) { form . append ( "prefix" , req . prefix ) ; } if ( req . access != null ) { form . append ( "access" , req . access ) ; } if ( req . headers != null ) { for ( const name of Object . keys ( req . headers ) ) { const value = req . headers [ name ] ; form . append ( name , value ) ; } } const contentType = ( req . headers || { } ) [ "content-type" ] ; if ( contentType == null ) { throw new Error ( "content-type is required to upload asset" ) ; } form . append ( "file" , { uri , type : contentType , name : "filename" } as any ) ; return new Promise ( ( resolve , reject ) => { const xhr = new XMLHttpRequest ( ) ; xhr . responseType = "json" ; xhr . onload = function ( ) { const jsonBody = xhr . response ; if ( jsonBody [ "result" ] ) { resolve ( jsonBody [ "result" ] [ "asset_name" ] ) ; } else if ( jsonBody [ "error" ] ) { reject ( decodeError ( jsonBody [ "error" ] ) ) ; } else { reject ( decodeError ( ) ) ; } } ; xhr . onerror = function ( ) { reject ( new TypeError ( "Network request failed" ) ) ; } ; xhr . ontimeout = function ( ) { reject ( new TypeError ( "Network request failed" ) ) ; } ; xhr . open ( "POST" , url , true ) ; xhr . setRequestHeader ( "Content-Type" , "multipart/form-data" ) ; if ( xhr . upload != null ) { xhr . upload . onprogress = function ( e ) { if ( onUploadProgress != null ) { onUploadProgress ( e ) ; } } ; } xhr . send ( form ) ; } ) ; } public export interface UploadAssetOptions { /**
     * The asset name prefix.
     */ prefix ? : string ; /**
     * The access control type of asset.
     */ access ? : "public" | "private" ; /**
     * Additional HTTP headers to be returned with the asset.
     */ headers : { "content-type" : string ; [ name ] : string ; } ; /**
     * Callback for reporting upload progress.
     */ onUploadProgress ? : ( e : ProgressEvent ) => void ; } public export class ReactNativeAssetContainer < T extends ReactNativeAPIClient > { parent : ReactNativeContainer < T > ; constructor ( parent ) { this . parent = parent ; } param uri param options returns async upload ( uri , options ) { const presignRequest = { } ; if ( options != null ) { presignRequest . prefix = options . prefix ; presignRequest . access = options . access ; if ( options . headers != null ) { presignRequest . headers = { ... options . headers } ; } } const presignRequestHeaders = presignRequest . headers || { } ; presignRequest . headers = presignRequestHeaders ; const { url } = await this . parent . apiClient . _presignUploadForm ( ) ; const asset_name = await uploadForm ( url , presignRequest , uri , options . onUploadProgress ) ; return asset_name ; } } public export class ReactNativeOIDCContainer < T extends ReactNativeAPIClient > extends OIDCContainer < T > { clientID : string ; isThirdParty : boolean ; constructor ( parent , auth ) { super ( parent , auth ) ; this . clientID = "" ; this . isThirdParty = true ; } async _setupCodeVerifier ( ) { const codeVerifier = await generateCodeVerifier ( ) ; const codeChallenge = await computeCodeChallenge ( codeVerifier ) ; return { verifier : codeVerifier , challenge : codeChallenge } ; } param options async authorize ( options ) { const redirectURIScheme = getCallbackURLScheme ( options . redirectURI ) ; const authorizeURL = await this . authorizeEndpoint ( options ) ; const redirectURL = await openAuthorizeURL ( authorizeURL , redirectURIScheme ) ; return this . _finishAuthorization ( redirectURL ) ; } /**
     * Open the URL with the user agent that is used to perform authentication.
     */ async openURL ( url ) { await openURL ( url ) ; } remarks param options async logout ( options = { } ) { return this . _logout ( options ) ; } /**
     * Authenticate as an anonymous user.
     */ async authenticateAnonymously ( ) { const { token } = await this . parent . apiClient . oauthChallenge ( "anonymous_request" ) ; const keyID = await this . parent . storage . getAnonymousKeyID ( this . parent . name ) ; const key = await getAnonymousJWK ( keyID ) ; const now = Math . floor ( + new Date ( ) / 1000 ) ; const header = { typ : "vnd.skygear.auth.anonymous-request" , ... key } ; const payload = { iat : + now , exp : + now + 60 , challenge : token , action : "auth" } ; const jwt = await signAnonymousJWT ( key . kid , header , payload ) ; const tokenResponse = await this . parent . apiClient . _oidcTokenRequest ( { grant_type : "urn:skygear-auth:params:oauth:grant-type:anonymous-request" , client_id : this . parent . apiClient . apiKey , jwt } ) ; const authResponse = await this . parent . apiClient . _oidcUserInfoRequest ( tokenResponse . access_token ) ; const ar = { ... authResponse } ; ar . accessToken = tokenResponse . access_token ; ar . refreshToken = tokenResponse . refresh_token ; ar . expiresIn = tokenResponse . expires_in ; await this . auth . persistAuthResponse ( ar ) ; await this . parent . storage . setAnonymousKeyID ( this . parent . name , key . kid ) ; return { user : authResponse . user } ; } param options async promoteAnonymousUser ( options ) { const keyID = await this . parent . storage . getAnonymousKeyID ( this . parent . name ) ; if ( ! keyID ) { throw new Error ( "anonymous user credentials not found" ) ; } const key = await getAnonymousJWK ( keyID ) ; const { token } = await this . parent . apiClient . oauthChallenge ( "anonymous_request" ) ; const now = Math . floor ( + new Date ( ) / 1000 ) ; const header = { typ : "vnd.skygear.auth.anonymous-request" , ... key } ; const payload = { iat : + now , exp : + now + 60 , challenge : token , action : "promote" } ; const jwt = await signAnonymousJWT ( key . kid , header , payload ) ; const loginHint = `https://auth.skygear.io/login_hint?type=anonymous&jwt=${ encodeURIComponent ( jwt ) }` ; const redirectURIScheme = getCallbackURLScheme ( options . redirectURI ) ; const authorizeURL = await this . authorizeEndpoint ( { ... options , prompt : "login" , loginHint } ) ; const redirectURL = await openAuthorizeURL ( authorizeURL , redirectURIScheme ) ; const result = await this . _finishAuthorization ( redirectURL ) ; await this . parent . storage . delAnonymousKeyID ( this . parent . name ) ; return result ; } } public export class ReactNativeAuthContainer < T extends ReactNativeAPIClient > extends AuthContainer < T > { async loginOAuthProvider ( providerID , callbackURL , options ? ) { return this . _performOAuth ( providerID , callbackURL , "login" , options ) ; } async linkOAuthProvider ( providerID , callbackURL ) { return this . _performOAuth ( providerID , callbackURL , "link" ) ; } async loginApple ( providerID , callbackURL , options ? ) { return this . _performSignInWithApple ( providerID , callbackURL , "login" , options ) ; } async linkApple ( providerID , callbackURL ) { return this . _performSignInWithApple ( providerID , callbackURL , "link" ) ; } async _performOAuth ( providerID , callbackURL , action , options ? ) { const callbackURLScheme = getCallbackURLScheme ( callbackURL ) ; const codeVerifier = await generateCodeVerifier ( ) ; const codeChallenge = await computeCodeChallenge ( codeVerifier ) ; const authURL = await this . parent . apiClient . oauthAuthorizationURL ( { providerID , codeChallenge , callbackURL : callbackURL , action , uxMode : "mobile_app" , onUserDuplicate : options && options . onUserDuplicate } ) ; const redirectURL = await openAuthorizeURL ( authURL , callbackURLScheme ) ; const j = extractResultFromURL ( redirectURL ) ; if ( j . result . error ) { throw decodeError ( j . result . error ) ; } const authorizationCode = j . result . result ; const p = this . parent . apiClient . getOAuthResult ( { authorizationCode , codeVerifier } ) ; return this . handleAuthResponse ( p ) ; } async _performSignInWithApple ( providerID , callbackURL , action , options ? ) { const codeVerifier = await generateCodeVerifier ( ) ; const codeChallenge = await computeCodeChallenge ( codeVerifier ) ; const authURL = await this . parent . apiClient . oauthAuthorizationURL ( { providerID , codeChallenge , callbackURL : callbackURL , action , uxMode : "manual" , onUserDuplicate : options && options . onUserDuplicate } ) ; const r1 = await fetch ( authURL ) ; const j1 = await r1 . json ( ) ; const appleURL = j1 . result ; const { code , scope , state } = await signInWithApple ( appleURL ) ; const authorizationCode = await this . parent . apiClient . oauthHandler ( { providerID , code , scope , state } ) ; const p = this . parent . apiClient . getOAuthResult ( { authorizationCode , codeVerifier } ) ; return this . handleAuthResponse ( p ) ; } } public export interface ConfigureOptions { /**
     * The OAuth client ID.
     */ clientID : string ; /**
     * The app endpoint.
     */ appEndpoint : string ; /**
     * The Skygear Auth endpoint. If it is omitted, it is derived by pre-pending `accounts.` to the domain of the app endpoint.
     */ authEndpoint ? : string ; /**
     * The Skygear asset endpoint. If it is omitted, it is derived by pre-pending `assets.` to the domain of the app endpoint.
     */ assetEndpoint ? : string ; } public export class ReactNativeContainer < T extends ReactNativeAPIClient > extends Container < T > { classicAuth : ReactNativeAuthContainer < T > ; asset : ReactNativeAssetContainer < T > ; auth : ReactNativeOIDCContainer < T > ; constructor ( options ? ) { const o = { ... options , apiClient : ( options && options . apiClient ) || new ReactNativeAPIClient ( ) , storage : ( options && options . storage ) || new GlobalJSONContainerStorage ( new ReactNativeAsyncStorageStorageDriver ( ) ) } as ContainerOptions < T > ; super ( o ) ; this . asset = new ReactNativeAssetContainer ( this ) ; this . classicAuth = new ReactNativeAuthContainer ( this ) ; this . auth = new ReactNativeOIDCContainer ( this , this . classicAuth ) ; } param options async configure ( options ) { await this . _configure ( { apiKey : options . clientID , endpoint : options . appEndpoint , authEndpoint : options . authEndpoint , assetEndpoint : options . assetEndpoint } ) ; this . auth . clientID = options . clientID ; } } remarks public const defaultContainer = new ReactNativeContainer ( ) ; export default defaultContainer ;