mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-18 09:45:11 +02:00
OAuth2 Support
Using CapacitorHttp and in-app link
This commit is contained in:
parent
6b164bdb27
commit
e521ddfab6
6 changed files with 177 additions and 74 deletions
|
@ -78,9 +78,32 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { App } from '@capacitor/app';
|
||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
import { CapacitorHttp } from '@capacitor/core'
|
import { CapacitorHttp } from '@capacitor/core'
|
||||||
import { OAuth2Client } from '@byteowls/capacitor-oauth2'
|
import { Browser } from '@capacitor/browser';
|
||||||
|
|
||||||
|
// Variable which is set to an instance of ServerConnectForm.vue used below of the listener
|
||||||
|
let serverConnectForm = null;
|
||||||
|
|
||||||
|
App.addListener('appUrlOpen', async (data) => {
|
||||||
|
// Handle the OAuth callback
|
||||||
|
const url = new URL(data.url)
|
||||||
|
|
||||||
|
// audiobookshelf://oauth?code...
|
||||||
|
if (url.host === 'oauth') {
|
||||||
|
// Extract oauth2 code to be exchanged for a token
|
||||||
|
const authCode = url.searchParams.get('code')
|
||||||
|
// Extract the state variable
|
||||||
|
const state = url.searchParams.get('state')
|
||||||
|
|
||||||
|
if (authCode) {
|
||||||
|
await serverConnectForm.oauthExchangeCodeForToken(authCode, state)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`[appUrlOpen] Unknown url: ${data.url}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -130,40 +153,103 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async clickLoginWithOpenId() {
|
async clickLoginWithOpenId() {
|
||||||
this.error = ''
|
serverConnectForm = this
|
||||||
const options = {
|
|
||||||
authorizationBaseUrl: `${this.serverConfig.address}/auth/openid`,
|
// First request that we want to do oauth/openid and get the URL which a browser window should open
|
||||||
logsEnabled: true,
|
const redirectUrl = await this.oauthRequest(this.serverConfig.address)
|
||||||
web: {
|
|
||||||
appId: 'com.audiobookshelf.web',
|
// Actually we should be able to use the redirectUrl directly for Browser.open below
|
||||||
responseType: 'token',
|
// However it seems that when directly using it there is a malformation and leads to the error
|
||||||
redirectUrl: location.origin
|
// Unhandled Promise Rejection: DataCloneError: The object can not be cloned.
|
||||||
},
|
// (On calling Browser.open)
|
||||||
android: {
|
// Which is hard to debug
|
||||||
appId: 'com.audiobookshelf.app',
|
// So we simply extract the important elements and build the required URL ourselves
|
||||||
responseType: 'code',
|
// which also has the advantage that we can replace the callbackurl with the app url
|
||||||
redirectUrl: 'com.audiobookshelf.app:/'
|
|
||||||
}
|
const client_id = redirectUrl.searchParams.get('client_id')
|
||||||
|
const scope = redirectUrl.searchParams.get('scope')
|
||||||
|
const state = redirectUrl.searchParams.get('state')
|
||||||
|
|
||||||
|
if (!client_id || !scope || !state) {
|
||||||
|
console.warn(`[SSO] Invalid OpenID URL - client_id scope or state missing: ${redirectUrl}`)
|
||||||
|
this.$toast.error(`SSO: Invalid answer`)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
OAuth2Client.authenticate(options)
|
|
||||||
.then(async (response) => {
|
const host = `${redirectUrl.protocol}//${redirectUrl.host}${redirectUrl.port ? ':' + redirectUrl.port : ''}`
|
||||||
const token = response.authorization_response?.additional_parameters?.setToken || response.authorization_response?.setToken
|
const buildUrl = `${host}${redirectUrl.pathname}?response_type=code` +
|
||||||
if (token) {
|
`&client_id=${encodeURIComponent(client_id)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(state)}` +
|
||||||
this.serverConfig.token = token
|
`&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}`
|
||||||
const payload = await this.authenticateToken()
|
|
||||||
if (payload) {
|
// example url for authentik
|
||||||
this.setUserAndConnection(payload)
|
// const authURL = "https://authentik/application/o/authorize/?response_type=code&client_id=41cd96f...&redirect_uri=audiobookshelf%3A%2F%2Foauth&scope=openid%20openid%20email%20profile&state=asdds...";
|
||||||
} else {
|
|
||||||
this.showAuth = true
|
// Open the browser. The browser/identity provider in turn will redirect to an in-app link supplementing a code
|
||||||
}
|
try {
|
||||||
} else {
|
await Browser.open({ url: buildUrl });
|
||||||
this.error = 'Invalid response: No token'
|
} catch (error) {
|
||||||
}
|
console.error("Error opening browser", error);
|
||||||
})
|
}
|
||||||
.catch((error) => {
|
},
|
||||||
console.error('OAuth rejected', error)
|
async oauthRequest(url) {
|
||||||
this.error = error.toString?.() || error.message
|
// set parameter isRest to true, so the backend wont attempt a redirect after we call backend:/callback in exchangeCodeForToken
|
||||||
|
// We dont need the callback parameter strictly speaking, but we must provide something or passport will error out as it seems to always expect it
|
||||||
|
const backendEndpoint = `${url}/auth/openid?callback=${encodeURIComponent('/login')}&isRest=true`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await CapacitorHttp.get({
|
||||||
|
url: backendEndpoint,
|
||||||
|
disableRedirects: true,
|
||||||
|
webFetchExtra: {
|
||||||
|
redirect: "manual"
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const locationHeader = response.headers["Location"]
|
||||||
|
if (locationHeader) {
|
||||||
|
const url = new URL(locationHeader)
|
||||||
|
return url
|
||||||
|
} else {
|
||||||
|
console.log('[SSO] No location header in oauthRequest')
|
||||||
|
this.$toast.error(`SSO: Invalid answer`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[SSO] Error in oauthRequest: ' + error)
|
||||||
|
this.$toast.error(`SSO error: ${error}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async oauthExchangeCodeForToken(code, state) {
|
||||||
|
// We need to read the url directly from this.serverConfig.address as the callback which is called via the external browser does not pass us that info
|
||||||
|
const backendEndpoint = `${this.serverConfig.address}/auth/openid/callback?state=${encodeURIComponent(state)}&code=${encodeURIComponent(code)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We can close the browser at this point
|
||||||
|
await Browser.close()
|
||||||
|
|
||||||
|
const response = await CapacitorHttp.get({
|
||||||
|
url: backendEndpoint
|
||||||
|
});
|
||||||
|
|
||||||
|
serverConnectForm.serverConfig.token = response.data.user.token
|
||||||
|
const payload = await serverConnectForm.authenticateToken()
|
||||||
|
|
||||||
|
if (!payload) {
|
||||||
|
console.log('[SSO] Failed getting token: ' + this.error);
|
||||||
|
this.$toast.error(`SSO error: ${this.error}`)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConnectForm.setUserAndConnection(payload)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[SSO] Error in exchangeCodeForToken: ' + error);
|
||||||
|
this.$toast.error(`SSO error: ${error}`)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
addCustomHeaders() {
|
addCustomHeaders() {
|
||||||
this.showAddCustomHeaders = true
|
this.showAddCustomHeaders = true
|
||||||
|
|
|
@ -2,6 +2,17 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.audiobookshelf.app</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>audiobookshelf</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
|
|
|
@ -11,8 +11,8 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'ByteowlsCapacitorOauth2', :path => '../../node_modules/@byteowls/capacitor-oauth2'
|
|
||||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||||
|
pod 'CapacitorBrowser', :path => '../../node_modules/@capacitor/browser'
|
||||||
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
||||||
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
|
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
|
||||||
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
PODS:
|
PODS:
|
||||||
- Alamofire (5.6.4)
|
- Alamofire (5.6.4)
|
||||||
- Capacitor (4.8.0):
|
- Capacitor (5.4.0):
|
||||||
- CapacitorCordova
|
- CapacitorCordova
|
||||||
- CapacitorApp (4.1.1):
|
- CapacitorApp (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorClipboard (4.1.0):
|
- CapacitorBrowser (5.1.0):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorCordova (4.8.0)
|
- CapacitorClipboard (5.0.6):
|
||||||
- CapacitorDialog (4.1.0):
|
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorHaptics (4.1.0):
|
- CapacitorCordova (5.4.0)
|
||||||
|
- CapacitorDialog (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorNetwork (4.1.0):
|
- CapacitorHaptics (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorPreferences (4.0.2):
|
- CapacitorNetwork (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorStatusBar (4.1.1):
|
- CapacitorPreferences (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CordovaPlugins (4.8.0):
|
- CapacitorStatusBar (5.0.6):
|
||||||
|
- Capacitor
|
||||||
|
- CordovaPlugins (5.4.0):
|
||||||
- CapacitorCordova
|
- CapacitorCordova
|
||||||
- Realm (10.36.0):
|
- Realm (10.36.0):
|
||||||
- Realm/Headers (= 10.36.0)
|
- Realm/Headers (= 10.36.0)
|
||||||
|
@ -29,6 +31,7 @@ DEPENDENCIES:
|
||||||
- Alamofire (~> 5.5)
|
- Alamofire (~> 5.5)
|
||||||
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
||||||
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||||
|
- "CapacitorBrowser (from `../../node_modules/@capacitor/browser`)"
|
||||||
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
|
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
|
||||||
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||||
- "CapacitorDialog (from `../../node_modules/@capacitor/dialog`)"
|
- "CapacitorDialog (from `../../node_modules/@capacitor/dialog`)"
|
||||||
|
@ -50,6 +53,8 @@ EXTERNAL SOURCES:
|
||||||
:path: "../../node_modules/@capacitor/ios"
|
:path: "../../node_modules/@capacitor/ios"
|
||||||
CapacitorApp:
|
CapacitorApp:
|
||||||
:path: "../../node_modules/@capacitor/app"
|
:path: "../../node_modules/@capacitor/app"
|
||||||
|
CapacitorBrowser:
|
||||||
|
:path: "../../node_modules/@capacitor/browser"
|
||||||
CapacitorClipboard:
|
CapacitorClipboard:
|
||||||
:path: "../../node_modules/@capacitor/clipboard"
|
:path: "../../node_modules/@capacitor/clipboard"
|
||||||
CapacitorCordova:
|
CapacitorCordova:
|
||||||
|
@ -69,19 +74,20 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c
|
Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c
|
||||||
Capacitor: 6002aadd64492438e5242325025045235dcb7e84
|
Capacitor: a5cd803e02b471591c81165f400ace01f40b11d3
|
||||||
CapacitorApp: acd42fe8561fe751ad5b5f459aa85e6acd7bee24
|
CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a
|
||||||
CapacitorClipboard: 4c092a2608520afb799429e63820256c3a59f1e5
|
CapacitorBrowser: 7a0fb6a1011abfaaf2dfedfd8248f942a8eda3d6
|
||||||
CapacitorCordova: c6249dcb2cf04dd835c0e99df1df4b9c8ad997e2
|
CapacitorClipboard: 77edf49827ea21da2a9c05c690a4a6a4d07199c4
|
||||||
CapacitorDialog: c8a6558d29767e76a32a056bb5e0fc9104b985b0
|
CapacitorCordova: 66ce22f9976de30fd816f746e9e92e07d6befafd
|
||||||
CapacitorHaptics: 213b3a1f3efd6dbf6e6b76a1b2bb0399cf43b213
|
CapacitorDialog: 0f3c15dfe9414b83bc64aef4078f1b92bcfead26
|
||||||
CapacitorNetwork: 7126b3d2d23ca60d5ac0d8d2ecccfab0b1f305c6
|
CapacitorHaptics: 1fffc1217c7e64a472d7845be50fb0c2f7d4204c
|
||||||
CapacitorPreferences: 1d66dc32299f55ed632c5611f312878979275ea5
|
CapacitorNetwork: d80b3e79bef6ec37640ee2806c19771f07ff2d0c
|
||||||
CapacitorStatusBar: 65933e554bb5d65b361deaa936a93616086a2608
|
CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10
|
||||||
CordovaPlugins: b7ac282a1681fad663e14dcbe719249f738b88ce
|
CapacitorStatusBar: 565c0a1ebd79bb40d797606a8992b4a105885309
|
||||||
|
CordovaPlugins: a5db67e5ac1061b9869a0efd754f2c2f776aeccc
|
||||||
Realm: 3fd136cb4c83a927482a7f1612496d37beed3cf5
|
Realm: 3fd136cb4c83a927482a7f1612496d37beed3cf5
|
||||||
RealmSwift: 513d4dcbf5bfc4d573454088b592685fc48dd716
|
RealmSwift: 513d4dcbf5bfc4d573454088b592685fc48dd716
|
||||||
|
|
||||||
PODFILE CHECKSUM: 05c80969578f3260e71d903c6ddb969847bcceb2
|
PODFILE CHECKSUM: 7a8fc177ef0646dd60a1ee8aa387964975fcc1e3
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.12.0
|
||||||
|
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -8,9 +8,9 @@
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.66-beta",
|
"version": "0.9.66-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@byteowls/capacitor-oauth2": "^5.0.0",
|
|
||||||
"@capacitor/android": "^5.0.0",
|
"@capacitor/android": "^5.0.0",
|
||||||
"@capacitor/app": "^5.0.0",
|
"@capacitor/app": "^5.0.6",
|
||||||
|
"@capacitor/browser": "^5.1.0",
|
||||||
"@capacitor/clipboard": "^5.0.0",
|
"@capacitor/clipboard": "^5.0.0",
|
||||||
"@capacitor/core": "^5.0.0",
|
"@capacitor/core": "^5.0.0",
|
||||||
"@capacitor/dialog": "^5.0.0",
|
"@capacitor/dialog": "^5.0.0",
|
||||||
|
@ -1939,14 +1939,6 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@byteowls/capacitor-oauth2": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@byteowls/capacitor-oauth2/-/capacitor-oauth2-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-yW50GypmyPJcH/95NwR2jJcgT78vBN3FYKL2w6A3vrT04bRLQyw2K0fLqfj8Zws6DJy43Ck1wPs0Bcdvbsub7A==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@capacitor/core": ">=5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@capacitor/android": {
|
"node_modules/@capacitor/android": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.4.0.tgz",
|
||||||
|
@ -1963,6 +1955,14 @@
|
||||||
"@capacitor/core": "^5.0.0"
|
"@capacitor/core": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor/browser": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/browser/-/browser-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-7togqchk2Tvq4SmLaWhcrd4x48ES/GEZsceM+29aun7WhxQEVcDU0cJsVdSU2LNFwNhWgPV2GW90etVd1B3OdQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor/cli": {
|
"node_modules/@capacitor/cli": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-5.4.0.tgz",
|
||||||
|
@ -21078,12 +21078,6 @@
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@byteowls/capacitor-oauth2": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@byteowls/capacitor-oauth2/-/capacitor-oauth2-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-yW50GypmyPJcH/95NwR2jJcgT78vBN3FYKL2w6A3vrT04bRLQyw2K0fLqfj8Zws6DJy43Ck1wPs0Bcdvbsub7A==",
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"@capacitor/android": {
|
"@capacitor/android": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.4.0.tgz",
|
||||||
|
@ -21096,6 +21090,12 @@
|
||||||
"integrity": "sha512-6ZXVdnNmaYILasC/RjQw+yfTmq2ZO7Q3v5lFcDVfq3PFGnybyYQh+RstBrYri+376OmXOXxBD7E6UxBhrMzXGA==",
|
"integrity": "sha512-6ZXVdnNmaYILasC/RjQw+yfTmq2ZO7Q3v5lFcDVfq3PFGnybyYQh+RstBrYri+376OmXOXxBD7E6UxBhrMzXGA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"@capacitor/browser": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/browser/-/browser-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-7togqchk2Tvq4SmLaWhcrd4x48ES/GEZsceM+29aun7WhxQEVcDU0cJsVdSU2LNFwNhWgPV2GW90etVd1B3OdQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@capacitor/cli": {
|
"@capacitor/cli": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-5.4.0.tgz",
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
"ionic:serve": "npm run start"
|
"ionic:serve": "npm run start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@byteowls/capacitor-oauth2": "^5.0.0",
|
|
||||||
"@capacitor/android": "^5.0.0",
|
"@capacitor/android": "^5.0.0",
|
||||||
"@capacitor/app": "^5.0.0",
|
"@capacitor/app": "^5.0.6",
|
||||||
|
"@capacitor/browser": "^5.1.0",
|
||||||
"@capacitor/clipboard": "^5.0.0",
|
"@capacitor/clipboard": "^5.0.0",
|
||||||
"@capacitor/core": "^5.0.0",
|
"@capacitor/core": "^5.0.0",
|
||||||
"@capacitor/dialog": "^5.0.0",
|
"@capacitor/dialog": "^5.0.0",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue