+1
-1
.eslintrc.js
+1
-1
.eslintrc.js
+4
android/build.gradle
+4
android/build.gradle
+70
android/src/main/java/expo/modules/atprotoauth/Crypto.kt
+70
android/src/main/java/expo/modules/atprotoauth/Crypto.kt
···
1
+
package expo.modules.expoatprotoauth
2
+
3
+
import com.nimbusds.jose.Algorithm
4
+
import com.nimbusds.jose.jwk.Curve
5
+
import com.nimbusds.jose.jwk.ECKey
6
+
import com.nimbusds.jose.jwk.KeyUse
7
+
import com.nimbusds.jose.util.Base64URL
8
+
import expo.modules.atprotoauth.EncodedJWK
9
+
import java.security.KeyPairGenerator
10
+
import java.security.MessageDigest
11
+
import java.security.interfaces.ECPrivateKey
12
+
import java.security.interfaces.ECPublicKey
13
+
import java.util.UUID
14
+
15
+
class Crypto {
16
+
fun digest(data: ByteArray): ByteArray {
17
+
val instance = MessageDigest.getInstance("sha256")
18
+
return instance.digest(data)
19
+
}
20
+
21
+
fun getRandomValues(byteLength: Int): ByteArray {
22
+
val random = ByteArray(byteLength)
23
+
java.security.SecureRandom().nextBytes(random)
24
+
return random
25
+
}
26
+
27
+
fun generateJwk(): EncodedJWK {
28
+
val keyIdString = UUID.randomUUID().toString()
29
+
30
+
val keyPairGen = KeyPairGenerator.getInstance("EC")
31
+
keyPairGen.initialize(Curve.P_256.toECParameterSpec())
32
+
val keyPair = keyPairGen.generateKeyPair()
33
+
34
+
val publicKey = keyPair.public as ECPublicKey
35
+
val privateKey = keyPair.private as ECPrivateKey
36
+
37
+
val privateJwk =
38
+
ECKey
39
+
.Builder(Curve.P_256, publicKey)
40
+
.privateKey(privateKey)
41
+
.keyUse(KeyUse.SIGNATURE)
42
+
.keyID(keyIdString)
43
+
.algorithm(Algorithm.parse("ES256"))
44
+
.build()
45
+
46
+
return EncodedJWK().apply {
47
+
kty = privateJwk.keyType.value
48
+
use = "sig"
49
+
crv = privateJwk.curve.toString()
50
+
kid = keyIdString
51
+
x = privateJwk.x.toString()
52
+
y = privateJwk.y.toString()
53
+
d = privateJwk.d.toString()
54
+
alg = privateJwk.algorithm.name
55
+
}
56
+
}
57
+
58
+
fun decodeJwk(encodedJwk: EncodedJWK): ECKey {
59
+
val xb64url = Base64URL.from(encodedJwk.x)
60
+
val yb64url = Base64URL.from(encodedJwk.y)
61
+
val db64url = Base64URL.from(encodedJwk.d)
62
+
return ECKey
63
+
.Builder(Curve.P_256, xb64url, yb64url)
64
+
.d(db64url)
65
+
.keyUse(KeyUse.SIGNATURE)
66
+
.keyID(encodedJwk.kid)
67
+
.algorithm(Algorithm.parse(encodedJwk.alg))
68
+
.build()
69
+
}
70
+
}
+27
-36
android/src/main/java/expo/modules/atprotoauth/ExpoAtprotoAuthModule.kt
+27
-36
android/src/main/java/expo/modules/atprotoauth/ExpoAtprotoAuthModule.kt
···
1
1
package expo.modules.atprotoauth
2
2
3
+
import expo.modules.expoatprotoauth.Crypto
4
+
import expo.modules.expoatprotoauth.Jose
3
5
import expo.modules.kotlin.modules.Module
4
6
import expo.modules.kotlin.modules.ModuleDefinition
5
-
import java.net.URL
6
7
7
8
class ExpoAtprotoAuthModule : Module() {
8
-
// Each module class must implement the definition function. The definition consists of components
9
-
// that describes the module's functionality and behavior.
10
-
// See https://docs.expo.dev/modules/module-api for more details about available components.
11
-
override fun definition() = ModuleDefinition {
12
-
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
13
-
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
14
-
// The module will be accessible from `requireNativeModule('ExpoAtprotoAuth')` in JavaScript.
15
-
Name("ExpoAtprotoAuth")
9
+
override fun definition() =
10
+
ModuleDefinition {
11
+
Name("ExpoAtprotoAuth")
16
12
17
-
// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
18
-
Constants(
19
-
"PI" to Math.PI
20
-
)
13
+
Function("digest") { data: ByteArray, algo: String ->
14
+
if (algo != "sha256") {
15
+
throw IllegalArgumentException("Unsupported algorithm: $algo")
16
+
}
17
+
return@Function Crypto().digest(data)
18
+
}
21
19
22
-
// Defines event names that the module can send to JavaScript.
23
-
Events("onChange")
20
+
Function("getRandomValues") { byteLength: Int ->
21
+
return@Function Crypto().getRandomValues(byteLength)
22
+
}
24
23
25
-
// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
26
-
Function("hello") {
27
-
"Hello world! 👋"
28
-
}
24
+
Function("generatePrivateJwk") { algo: String ->
25
+
if (algo != "ES256") {
26
+
throw IllegalArgumentException("Unsupported algorithm: $algo")
27
+
}
28
+
return@Function Crypto().generateJwk()
29
+
}
29
30
30
-
// Defines a JavaScript function that always returns a Promise and whose native code
31
-
// is by default dispatched on the different thread than the JavaScript runtime runs on.
32
-
AsyncFunction("setValueAsync") { value: String ->
33
-
// Send an event to JavaScript.
34
-
sendEvent("onChange", mapOf(
35
-
"value" to value
36
-
))
37
-
}
31
+
Function("createJwt") { header: String, payload: String, encodedJwk: EncodedJWK ->
32
+
val jwk = Crypto().decodeJwk(encodedJwk)
33
+
return@Function Jose().createJwt(header, payload, jwk)
34
+
}
38
35
39
-
// Enables the module to be used as a native view. Definition components that are accepted as part of
40
-
// the view definition: Prop, Events.
41
-
View(ExpoAtprotoAuthView::class) {
42
-
// Defines a setter for the `url` prop.
43
-
Prop("url") { view: ExpoAtprotoAuthView, url: URL ->
44
-
view.webView.loadUrl(url.toString())
36
+
Function("verifyJwt") { token: String, encodedJwk: EncodedJWK, options: VerifyOptions ->
37
+
val jwk = Crypto().decodeJwk(encodedJwk)
38
+
return@Function Jose().verifyJwt(token, jwk, options)
45
39
}
46
-
// Defines an event that the view can send to JavaScript.
47
-
Events("onLoad")
48
40
}
49
-
}
50
41
}
-30
android/src/main/java/expo/modules/atprotoauth/ExpoAtprotoAuthView.kt
-30
android/src/main/java/expo/modules/atprotoauth/ExpoAtprotoAuthView.kt
···
1
-
package expo.modules.atprotoauth
2
-
3
-
import android.content.Context
4
-
import android.webkit.WebView
5
-
import android.webkit.WebViewClient
6
-
import expo.modules.kotlin.AppContext
7
-
import expo.modules.kotlin.viewevent.EventDispatcher
8
-
import expo.modules.kotlin.views.ExpoView
9
-
10
-
class ExpoAtprotoAuthView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
11
-
// Creates and initializes an event dispatcher for the `onLoad` event.
12
-
// The name of the event is inferred from the value and needs to match the event name defined in the module.
13
-
private val onLoad by EventDispatcher()
14
-
15
-
// Defines a WebView that will be used as the root subview.
16
-
internal val webView = WebView(context).apply {
17
-
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
18
-
webViewClient = object : WebViewClient() {
19
-
override fun onPageFinished(view: WebView, url: String) {
20
-
// Sends an event to JavaScript. Triggers a callback defined on the view component in JavaScript.
21
-
onLoad(mapOf("url" to url))
22
-
}
23
-
}
24
-
}
25
-
26
-
init {
27
-
// Adds the WebView to the view hierarchy.
28
-
addView(webView)
29
-
}
30
-
}
+116
android/src/main/java/expo/modules/atprotoauth/Jose.kt
+116
android/src/main/java/expo/modules/atprotoauth/Jose.kt
···
1
+
package expo.modules.expoatprotoauth
2
+
3
+
import com.nimbusds.jose.JWSHeader
4
+
import com.nimbusds.jose.crypto.ECDSASigner
5
+
import com.nimbusds.jose.crypto.ECDSAVerifier
6
+
import com.nimbusds.jose.jwk.ECKey
7
+
import com.nimbusds.jwt.JWTClaimsSet
8
+
import com.nimbusds.jwt.SignedJWT
9
+
import expo.modules.atprotoauth.VerifyOptions
10
+
import expo.modules.atprotoauth.VerifyResult
11
+
12
+
class InvalidPayloadException(
13
+
message: String,
14
+
) : Exception(message)
15
+
16
+
class Jose {
17
+
fun createJwt(
18
+
header: String,
19
+
payload: String,
20
+
jwk: ECKey,
21
+
): String {
22
+
val parsedHeader = JWSHeader.parse(header)
23
+
val parsedPayload = JWTClaimsSet.parse(payload)
24
+
25
+
val signer = ECDSASigner(jwk)
26
+
val jwt = SignedJWT(parsedHeader, parsedPayload)
27
+
jwt.sign(signer)
28
+
29
+
return jwt.serialize()
30
+
}
31
+
32
+
fun verifyJwt(
33
+
token: String,
34
+
jwk: ECKey,
35
+
options: VerifyOptions,
36
+
): VerifyResult {
37
+
val jwt = SignedJWT.parse(token)
38
+
val verifier = ECDSAVerifier(jwk)
39
+
40
+
if (!jwt.verify(verifier)) {
41
+
throw InvalidPayloadException("invalid JWT signature")
42
+
}
43
+
44
+
val protectedHeader = emptyMap<String, Any>().toMutableMap()
45
+
protectedHeader["alg"] = jwt.header.algorithm
46
+
47
+
jwt.header.getCustomParam("jku")?.let {
48
+
protectedHeader["jku"] = it.toString()
49
+
}
50
+
jwt.header.keyID?.let {
51
+
protectedHeader["kid"] = it
52
+
}
53
+
jwt.header.type?.let {
54
+
protectedHeader["typ"] = it.toString()
55
+
}
56
+
jwt.header.contentType?.let {
57
+
protectedHeader["cty"] = it
58
+
}
59
+
jwt.header.criticalParams?.let {
60
+
protectedHeader["crit"] = it.toList()
61
+
}
62
+
63
+
options.typ?.let {
64
+
if (jwt.header.type.toString() != it) {
65
+
throw InvalidPayloadException("typ mismatch")
66
+
}
67
+
}
68
+
69
+
val claims = jwt.jwtClaimsSet
70
+
71
+
options.requiredClaims?.let { requiredClaims ->
72
+
requiredClaims.forEach { claim ->
73
+
if (!claims.claims.containsKey(claim)) {
74
+
throw InvalidPayloadException("required claim '$claim' missing")
75
+
}
76
+
}
77
+
}
78
+
79
+
options.audience?.let {
80
+
if (!claims.audience.contains(it)) {
81
+
throw InvalidPayloadException("audience mismatch")
82
+
}
83
+
}
84
+
85
+
options.subject?.let {
86
+
if (claims.subject != it) {
87
+
throw InvalidPayloadException("subject mismatch")
88
+
}
89
+
}
90
+
91
+
options.checkTolerance?.let {
92
+
val currentTime = options.currentDate ?: (System.currentTimeMillis() / 1000.0)
93
+
if (claims.issueTime.time / 1000.0 + it < currentTime) {
94
+
throw InvalidPayloadException("token expired")
95
+
}
96
+
}
97
+
98
+
options.maxTokenAge?.let {
99
+
val currentTime = options.currentDate ?: (System.currentTimeMillis() / 1000.0)
100
+
if (claims.issueTime.time / 1000.0 + it < currentTime) {
101
+
throw InvalidPayloadException("token expired")
102
+
}
103
+
}
104
+
105
+
options.issuer?.let {
106
+
if (claims.issuer != it) {
107
+
throw InvalidPayloadException("issuer mismatch")
108
+
}
109
+
}
110
+
111
+
return VerifyResult().apply {
112
+
payload = jwt.payload.toString()
113
+
this.protectedHeader = protectedHeader
114
+
}
115
+
}
116
+
}
+64
android/src/main/java/expo/modules/atprotoauth/Records.kt
+64
android/src/main/java/expo/modules/atprotoauth/Records.kt
···
1
+
package expo.modules.atprotoauth
2
+
3
+
import expo.modules.kotlin.records.Field
4
+
import expo.modules.kotlin.records.Record
5
+
6
+
class EncodedJWK : Record {
7
+
@Field
8
+
var kty: String = ""
9
+
10
+
@Field
11
+
var use: String = ""
12
+
13
+
@Field
14
+
var crv: String = ""
15
+
16
+
@Field
17
+
var kid: String = ""
18
+
19
+
@Field
20
+
var x: String = ""
21
+
22
+
@Field
23
+
var y: String = ""
24
+
25
+
@Field
26
+
var d: String = ""
27
+
28
+
@Field
29
+
var alg: String = ""
30
+
}
31
+
32
+
class VerifyOptions : Record {
33
+
@Field
34
+
var audience: String? = null
35
+
36
+
@Field
37
+
var checkTolerance: Double? = null
38
+
39
+
@Field
40
+
var issuer: String? = null
41
+
42
+
@Field
43
+
var maxTokenAge: Double? = null
44
+
45
+
@Field
46
+
var subject: String? = null
47
+
48
+
@Field
49
+
var typ: String? = null
50
+
51
+
@Field
52
+
var currentDate: Double? = null
53
+
54
+
@Field
55
+
var requiredClaims: Array<String>? = null
56
+
}
57
+
58
+
class VerifyResult : Record {
59
+
@Field
60
+
var payload: String = ""
61
+
62
+
@Field
63
+
var protectedHeader: Map<String, Any> = emptyMap()
64
+
}
+21
-28
example/App.tsx
+21
-28
example/App.tsx
···
1
1
import React from 'react'
2
-
import { Text, View, StyleSheet, Button, Alert, TextInput } from 'react-native'
2
+
import {
3
+
Text,
4
+
View,
5
+
StyleSheet,
6
+
Button,
7
+
Alert,
8
+
TextInput,
9
+
Platform,
10
+
} from 'react-native'
3
11
import {
4
12
digest,
5
13
getRandomValues,
···
10
18
import { OAuthSession } from '@atproto/oauth-client'
11
19
import { Agent } from '@atproto/api'
12
20
import type { ReactNativeKey } from 'expo-atproto-auth'
13
-
import * as Browser from 'expo-web-browser'
14
21
15
22
const client = new ReactNativeOAuthClient({
16
23
clientMetadata: {
···
109
116
<Button
110
117
title="Open Sign In"
111
118
onPress={async () => {
112
-
let url: URL
113
-
try {
114
-
url = await client.authorize(input ?? '')
115
-
} catch (e: any) {
116
-
Alert.alert('Error', e.toString())
117
-
return
118
-
}
119
-
const res = await Browser.openAuthSessionAsync(
120
-
url.toString(),
121
-
'at.hailey://auth/callback'
122
-
)
123
-
124
-
if (res.type === 'success') {
125
-
const resUrl = new URL(res.url)
126
-
try {
127
-
const params = new URLSearchParams(resUrl.hash.substring(1))
128
-
const callbackRes = await client.callback(params)
129
-
setSession(callbackRes.session)
130
-
131
-
const newAgent = new Agent(callbackRes.session)
132
-
setAgent(newAgent)
133
-
} catch (e: any) {
134
-
Alert.alert('Error', e.toString())
135
-
}
119
+
const res = await client.signIn(input ?? '')
120
+
if (res.status === 'success') {
121
+
setSession(res.session)
122
+
const newAgent = new Agent(res.session)
123
+
setAgent(newAgent)
124
+
} else if (res.status === 'error') {
125
+
Alert.alert('Error', (res.error as any).toString())
136
126
} else {
137
-
Alert.alert('Error', `Received non-success status: ${res.type}`)
127
+
Alert.alert(
128
+
'Error',
129
+
`Received unknown WebResultType: ${res.status}`
130
+
)
138
131
}
139
132
}}
140
133
/>
···
177
170
onPress={async () => {
178
171
try {
179
172
await agent?.post({
180
-
text: 'Test post from Expo Atproto Auth example',
173
+
text: `Test post from Expo Atproto Auth example using platform ${Platform.OS}`,
181
174
})
182
175
} catch (e: any) {
183
176
Alert.alert('Error', e.toString())
+2
-2
example/ios/expoatprotoauthexample.xcodeproj/project.pbxproj
+2
-2
example/ios/expoatprotoauthexample.xcodeproj/project.pbxproj
···
351
351
);
352
352
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
353
353
PRODUCT_BUNDLE_IDENTIFIER = expo.modules.atprotoauth.example;
354
-
PRODUCT_NAME = expoatprotoauthexample;
354
+
PRODUCT_NAME = "expoatprotoauthexample";
355
355
SWIFT_OBJC_BRIDGING_HEADER = "expoatprotoauthexample/expoatprotoauthexample-Bridging-Header.h";
356
356
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
357
357
SWIFT_VERSION = 5.0;
···
382
382
);
383
383
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
384
384
PRODUCT_BUNDLE_IDENTIFIER = expo.modules.atprotoauth.example;
385
-
PRODUCT_NAME = expoatprotoauthexample;
385
+
PRODUCT_NAME = "expoatprotoauthexample";
386
386
SWIFT_OBJC_BRIDGING_HEADER = "expoatprotoauthexample/expoatprotoauthexample-Bridging-Header.h";
387
387
SWIFT_VERSION = 5.0;
388
388
TARGETED_DEVICE_FAMILY = "1,2";
+3
-3
ios/Crypto.swift
+3
-3
ios/Crypto.swift
···
13
13
return Data(bytes)
14
14
}
15
15
16
-
static func generateJwk() -> JWK {
16
+
static func generateJwk() -> EncodedJWK {
17
17
let kid = UUID().uuidString
18
18
19
19
let privKey = P256.Signing.PrivateKey()
···
23
23
let y = pubKey.x963Representation[33...].base64URLEncodedString()
24
24
let d = privKey.rawRepresentation.base64URLEncodedString()
25
25
26
-
let jwk = JWK()
26
+
let jwk = EncodedJWK()
27
27
jwk.kty = "EC"
28
28
jwk.use = "sig"
29
29
jwk.crv = "P-256"
···
36
36
return jwk
37
37
}
38
38
39
-
static func importJwk(x: String, y: String, d: String) throws -> SecKey {
39
+
static func decodeJwk(x: String, y: String, d: String) throws -> SecKey {
40
40
func base64UrlDecode(_ string: String) -> Data? {
41
41
var base64 = string
42
42
.replacingOccurrences(of: "-", with: "+")
+7
-13
ios/ExpoAtprotoAuthModule.swift
+7
-13
ios/ExpoAtprotoAuthModule.swift
···
23
23
return CryptoUtil.getRandomValues(byteLength: byteLength)
24
24
}
25
25
26
-
Function("generatePrivateJwk") { (algo: String) throws -> JWK in
26
+
Function("generatePrivateJwk") { (algo: String) throws -> EncodedJWK in
27
27
if algo != "ES256" {
28
28
throw ExpoAtprotoAuthError.unsupportedAlgorithm(algo)
29
29
}
30
30
return CryptoUtil.generateJwk()
31
31
}
32
32
33
-
Function("createJwt") { (header: String, payload: String, jwk: JWK) throws -> String in
34
-
let key = try CryptoUtil.importJwk(x: jwk.x, y: jwk.y, d: jwk.d)
35
-
let jwt = try JoseUtil.createJwt(header: header, payload: payload, jwk: key)
33
+
Function("createJwt") { (header: String, payload: String, jwk: EncodedJWK) throws -> String in
34
+
let jwk = try CryptoUtil.decodeJwk(x: jwk.x, y: jwk.y, d: jwk.d)
35
+
let jwt = try JoseUtil.createJwt(header: header, payload: payload, jwk: jwk)
36
36
return jwt
37
37
}
38
38
39
-
Function("verifyJwt") { (token: String, jwk: JWK, options: VerifyOptions) throws -> VerifyResponse in
40
-
let key = try CryptoUtil.importJwk(x: jwk.x, y: jwk.y, d: jwk.d)
41
-
let res = try JoseUtil.verifyJwt(token: token, jwk: key, options: options)
39
+
Function("verifyJwt") { (token: String, jwk: EncodedJWK, options: VerifyOptions) throws -> VerifyResult in
40
+
let jwk = try CryptoUtil.decodeJwk(x: jwk.x, y: jwk.y, d: jwk.d)
41
+
let res = try JoseUtil.verifyJwt(token: token, jwk: jwk, options: options)
42
42
return res
43
-
}
44
-
45
-
AsyncFunction("setValueAsync") { (value: String) in
46
-
self.sendEvent("onChange", [
47
-
"value": value
48
-
])
49
43
}
50
44
}
51
45
}
+2
-2
ios/Jose.swift
+2
-2
ios/Jose.swift
···
35
35
return jws.compactSerializedString
36
36
}
37
37
38
-
static func verifyJwt(token: String, jwk: SecKey, options: VerifyOptions) throws -> VerifyResponse {
38
+
static func verifyJwt(token: String, jwk: SecKey, options: VerifyOptions) throws -> VerifyResult {
39
39
guard let jws = try? JWS(compactSerialization: token),
40
40
let verifier = Verifier(verifyingAlgorithm: .ES256, key: jwk),
41
41
let validation = try? jws.validate(using: verifier)
···
128
128
}
129
129
}
130
130
131
-
let res = VerifyResponse()
131
+
let res = VerifyResult()
132
132
res.payload = payload
133
133
res.protectedHeader = protectedHeader
134
134
+2
-2
ios/Records.swift
+2
-2
ios/Records.swift
+3
-1
package.json
+3
-1
package.json
···
8
8
"build": "expo-module build",
9
9
"clean": "expo-module clean",
10
10
"typecheck": "tsc",
11
-
"lint": "eslint \"**/*.{js,ts,tsx}\"",
11
+
"lint": "eslint \"**/*.{ts,tsx}\"",
12
12
"test": "expo-module test",
13
13
"prepare": "expo-module prepare",
14
14
"prepublishOnly": "expo-module prepublishOnly",
···
43
43
"eslint-plugin-prettier": "^5.2.3",
44
44
"expo": "~53.0.0",
45
45
"expo-module-scripts": "^4.1.9",
46
+
"expo-web-browser": "^14.2.0",
46
47
"jest": "^29.7.0",
47
48
"patch-package": "^8.0.0",
48
49
"postinstall-postinstall": "^2.1.0",
···
53
54
"peerDependencies": {
54
55
"@atproto/oauth-client": "*",
55
56
"expo": "*",
57
+
"expo-web-browser": "*",
56
58
"react": "*",
57
59
"react-native": "*",
58
60
"react-native-mmkv": "*"
+1
-1
src/ExpoAtprotoAuth.types.ts
+1
-1
src/ExpoAtprotoAuth.types.ts
+1
-1
src/index.ts
+1
-1
src/index.ts
+35
-1
src/react-native-oauth-client.ts
+35
-1
src/react-native-oauth-client.ts
···
5
5
type OAuthResponseMode,
6
6
atprotoLoopbackClientMetadata,
7
7
OAuthClient,
8
+
OAuthSession,
8
9
} from '@atproto/oauth-client'
9
10
import { ReactNativeRuntimeImplementation } from './react-native-runtime-implementation'
10
11
import { ReactNativeOAuthDatabase } from './react-native-oauth-database'
12
+
import { openAuthSessionAsync, WebBrowserResultType } from 'expo-web-browser'
11
13
12
14
export type Simplify<T> = { [K in keyof T]: T[K] } & NonNullable<unknown>
13
15
···
33
35
>
34
36
>
35
37
36
-
export class ReactNativeOAuthClient extends OAuthClient {
38
+
export class ExpoOAuthClient extends OAuthClient {
37
39
constructor({
38
40
responseMode = 'fragment',
39
41
...options
···
65
67
protectedResourceMetadataCache:
66
68
database.getProtectedResourceMetadataCache(),
67
69
})
70
+
}
71
+
72
+
async signIn(
73
+
input: string
74
+
): Promise<
75
+
| { status: WebBrowserResultType }
76
+
| { status: 'error'; error: unknown }
77
+
| { status: 'success'; session: OAuthSession }
78
+
> {
79
+
let url: URL
80
+
try {
81
+
url = await this.authorize(input)
82
+
} catch (e: unknown) {
83
+
return { status: 'error', error: e }
84
+
}
85
+
86
+
const res = await openAuthSessionAsync(
87
+
url.toString(),
88
+
this.clientMetadata.redirect_uris[0],
89
+
{
90
+
createTask: false,
91
+
}
92
+
)
93
+
94
+
if (res.type === 'success') {
95
+
const resUrl = new URL(res.url)
96
+
const params = new URLSearchParams(resUrl.hash.substring(1))
97
+
const callbackRes = await this.callback(params)
98
+
return { status: 'success', session: callbackRes.session }
99
+
} else {
100
+
return { status: res.type }
101
+
}
68
102
}
69
103
}
+5
yarn.lock
+5
yarn.lock
···
4350
4350
dependencies:
4351
4351
invariant "^2.2.4"
4352
4352
4353
+
expo-web-browser@^14.2.0:
4354
+
version "14.2.0"
4355
+
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.2.0.tgz#d8fb521ae349aebbf5c0ca32448877480124c06c"
4356
+
integrity sha512-6S51d8pVlDRDsgGAp8BPpwnxtyKiMWEFdezNz+5jVIyT+ctReW42uxnjRgtsdn5sXaqzhaX+Tzk/CWaKCyC0hw==
4357
+
4353
4358
expo@~53.0.0:
4354
4359
version "53.0.19"
4355
4360
resolved "https://registry.yarnpkg.com/expo/-/expo-53.0.19.tgz#69f8ed224efadf517d3ff66dde3d536fb3e8f00a"