@jbrowse/plugin-authentication 2.4.2 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DropboxOAuthModel/configSchema.d.ts +105 -1
- package/dist/DropboxOAuthModel/configSchema.js +0 -8
- package/dist/DropboxOAuthModel/configSchema.js.map +1 -1
- package/dist/DropboxOAuthModel/model.d.ts +304 -8
- package/dist/DropboxOAuthModel/model.js +26 -35
- package/dist/DropboxOAuthModel/model.js.map +1 -1
- package/dist/DropboxOAuthModel/util.d.ts +1 -0
- package/dist/DropboxOAuthModel/util.js +28 -0
- package/dist/DropboxOAuthModel/util.js.map +1 -0
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.d.ts +2 -2
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.js +10 -11
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -1
- package/dist/ExternalTokenModel/configSchema.d.ts +36 -1
- package/dist/ExternalTokenModel/model.d.ts +93 -3
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +15 -0
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +20 -0
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +1 -0
- package/dist/GoogleDriveOAuthModel/configSchema.d.ts +105 -1
- package/dist/GoogleDriveOAuthModel/model.d.ts +219 -20
- package/dist/GoogleDriveOAuthModel/model.js +38 -51
- package/dist/GoogleDriveOAuthModel/model.js.map +1 -1
- package/dist/GoogleDriveOAuthModel/util.d.ts +1 -0
- package/dist/GoogleDriveOAuthModel/util.js +17 -0
- package/dist/GoogleDriveOAuthModel/util.js.map +1 -0
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.d.ts +2 -2
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.js +17 -21
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -1
- package/dist/HTTPBasicModel/configSchema.d.ts +44 -1
- package/dist/HTTPBasicModel/model.d.ts +135 -3
- package/dist/HTTPBasicModel/model.js +24 -9
- package/dist/HTTPBasicModel/model.js.map +1 -1
- package/dist/OAuthModel/configSchema.d.ts +94 -1
- package/dist/OAuthModel/configSchema.js +1 -9
- package/dist/OAuthModel/configSchema.js.map +1 -1
- package/dist/OAuthModel/model.d.ts +263 -8
- package/dist/OAuthModel/model.js +165 -113
- package/dist/OAuthModel/model.js.map +1 -1
- package/dist/OAuthModel/util.d.ts +7 -0
- package/dist/OAuthModel/util.js +60 -0
- package/dist/OAuthModel/util.js.map +1 -0
- package/dist/index.d.ts +954 -105
- package/dist/util.d.ts +6 -0
- package/dist/util.js +23 -0
- package/dist/util.js.map +1 -0
- package/esm/DropboxOAuthModel/configSchema.d.ts +105 -1
- package/esm/DropboxOAuthModel/configSchema.js +0 -8
- package/esm/DropboxOAuthModel/configSchema.js.map +1 -1
- package/esm/DropboxOAuthModel/model.d.ts +304 -8
- package/esm/DropboxOAuthModel/model.js +26 -35
- package/esm/DropboxOAuthModel/model.js.map +1 -1
- package/esm/DropboxOAuthModel/util.d.ts +1 -0
- package/esm/DropboxOAuthModel/util.js +24 -0
- package/esm/DropboxOAuthModel/util.js.map +1 -0
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.d.ts +2 -2
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js +10 -11
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -1
- package/esm/ExternalTokenModel/configSchema.d.ts +36 -1
- package/esm/ExternalTokenModel/model.d.ts +93 -3
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +15 -0
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +16 -0
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +1 -0
- package/esm/GoogleDriveOAuthModel/configSchema.d.ts +105 -1
- package/esm/GoogleDriveOAuthModel/model.d.ts +219 -20
- package/esm/GoogleDriveOAuthModel/model.js +37 -49
- package/esm/GoogleDriveOAuthModel/model.js.map +1 -1
- package/esm/GoogleDriveOAuthModel/util.d.ts +1 -0
- package/esm/GoogleDriveOAuthModel/util.js +13 -0
- package/esm/GoogleDriveOAuthModel/util.js.map +1 -0
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.d.ts +2 -2
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js +18 -22
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -1
- package/esm/HTTPBasicModel/configSchema.d.ts +44 -1
- package/esm/HTTPBasicModel/model.d.ts +135 -3
- package/esm/HTTPBasicModel/model.js +24 -9
- package/esm/HTTPBasicModel/model.js.map +1 -1
- package/esm/OAuthModel/configSchema.d.ts +94 -1
- package/esm/OAuthModel/configSchema.js +1 -9
- package/esm/OAuthModel/configSchema.js.map +1 -1
- package/esm/OAuthModel/model.d.ts +263 -8
- package/esm/OAuthModel/model.js +164 -86
- package/esm/OAuthModel/model.js.map +1 -1
- package/esm/OAuthModel/util.d.ts +7 -0
- package/esm/OAuthModel/util.js +30 -0
- package/esm/OAuthModel/util.js.map +1 -0
- package/esm/index.d.ts +954 -105
- package/esm/util.d.ts +6 -0
- package/esm/util.js +18 -0
- package/esm/util.js.map +1 -0
- package/package.json +3 -4
- package/src/DropboxOAuthModel/configSchema.ts +0 -8
- package/src/DropboxOAuthModel/model.tsx +35 -54
- package/src/DropboxOAuthModel/util.ts +36 -0
- package/src/ExternalTokenModel/ExternalTokenEntryForm.tsx +39 -41
- package/src/GoogleDriveOAuthModel/GoogleDriveFilehandle.ts +38 -0
- package/src/GoogleDriveOAuthModel/model.tsx +54 -104
- package/src/GoogleDriveOAuthModel/util.ts +29 -0
- package/src/HTTPBasicModel/HTTPBasicLoginForm.tsx +53 -56
- package/src/HTTPBasicModel/model.tsx +26 -11
- package/src/OAuthModel/configSchema.ts +2 -9
- package/src/OAuthModel/model.tsx +190 -108
- package/src/OAuthModel/util.ts +33 -0
- package/src/util.ts +25 -0
package/esm/util.d.ts
ADDED
package/esm/util.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function getResponseError({ response, reason, statusText, }) {
|
|
2
|
+
return [
|
|
3
|
+
`HTTP ${response.status}`,
|
|
4
|
+
reason,
|
|
5
|
+
statusText !== null && statusText !== void 0 ? statusText : (await getError(response)),
|
|
6
|
+
]
|
|
7
|
+
.filter(f => !!f)
|
|
8
|
+
.join(' - ');
|
|
9
|
+
}
|
|
10
|
+
export async function getError(response) {
|
|
11
|
+
try {
|
|
12
|
+
return response.text();
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return response.statusText;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=util.js.map
|
package/esm/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EACrC,QAAQ,EACR,MAAM,EACN,UAAU,GAKX;IACC,OAAO;QACL,QAAQ,QAAQ,CAAC,MAAM,EAAE;QACzB,MAAM;QACN,UAAU,aAAV,UAAU,cAAV,UAAU,GAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;KACzC;SACE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;SAChB,IAAI,CAAC,KAAK,CAAC,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAkB;IAC/C,IAAI;QACF,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAA;KACvB;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,QAAQ,CAAC,UAAU,CAAA;KAC3B;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-authentication",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "JBrowse 2 Authentication",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -38,8 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"crypto-js": "^3.0.0",
|
|
41
|
-
"generic-filehandle": "^3.0.0"
|
|
42
|
-
"jwt-decode": "^3.1.2"
|
|
41
|
+
"generic-filehandle": "^3.0.0"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
45
44
|
"@jbrowse/core": "^2.0.0",
|
|
@@ -57,5 +56,5 @@
|
|
|
57
56
|
"distModule": "esm/index.js",
|
|
58
57
|
"srcModule": "src/index.ts",
|
|
59
58
|
"module": "esm/index.js",
|
|
60
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "1cbe7ba097fb2d2763c776e5e429e4670cdd583c"
|
|
61
60
|
}
|
|
@@ -51,14 +51,6 @@ const DropboxOAuthConfigSchema = ConfigurationSchema(
|
|
|
51
51
|
'getdropbox.com',
|
|
52
52
|
],
|
|
53
53
|
},
|
|
54
|
-
/**
|
|
55
|
-
* #slot
|
|
56
|
-
*/
|
|
57
|
-
hasRefreshToken: {
|
|
58
|
-
description: 'true if the endpoint can supply a refresh token',
|
|
59
|
-
type: 'boolean',
|
|
60
|
-
defaultValue: true,
|
|
61
|
-
},
|
|
62
54
|
},
|
|
63
55
|
{
|
|
64
56
|
/**
|
|
@@ -3,26 +3,12 @@ import { ConfigurationReference } from '@jbrowse/core/configuration'
|
|
|
3
3
|
import { UriLocation } from '@jbrowse/core/util/types'
|
|
4
4
|
import { SvgIconProps, SvgIcon } from '@mui/material'
|
|
5
5
|
import { Instance, types } from 'mobx-state-tree'
|
|
6
|
+
|
|
7
|
+
// locals
|
|
6
8
|
import { DropboxOAuthInternetAccountConfigModel } from './configSchema'
|
|
7
9
|
import baseModel from '../OAuthModel/model'
|
|
8
10
|
import { configSchema as OAuthConfigSchema } from '../OAuthModel'
|
|
9
|
-
|
|
10
|
-
interface DropboxError {
|
|
11
|
-
error_summary: string
|
|
12
|
-
error: {
|
|
13
|
-
'.tag': string
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Error messages from https://www.dropbox.com/developers/documentation/http/documentation#sharing-get_shared_link_file */
|
|
18
|
-
const dropboxErrorMessages: Record<string, string | undefined> = {
|
|
19
|
-
shared_link_not_found: "The shared link wasn't found.",
|
|
20
|
-
shared_link_access_denied:
|
|
21
|
-
'The caller is not allowed to access this shared link.',
|
|
22
|
-
unsupported_link_type:
|
|
23
|
-
'This type of link is not supported; use files/export instead.',
|
|
24
|
-
shared_link_is_directory: 'Directories cannot be retrieved by this endpoint.',
|
|
25
|
-
}
|
|
11
|
+
import { getDescriptiveErrorMessage } from './util'
|
|
26
12
|
|
|
27
13
|
export function DropboxIcon(props: SvgIconProps) {
|
|
28
14
|
return (
|
|
@@ -32,55 +18,45 @@ export function DropboxIcon(props: SvgIconProps) {
|
|
|
32
18
|
)
|
|
33
19
|
}
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
errorMessage = await response.text()
|
|
39
|
-
} catch (error) {
|
|
40
|
-
errorMessage = ''
|
|
41
|
-
}
|
|
42
|
-
if (errorMessage) {
|
|
43
|
-
let errorMessageParsed: DropboxError | undefined
|
|
44
|
-
try {
|
|
45
|
-
errorMessageParsed = JSON.parse(errorMessage)
|
|
46
|
-
} catch (error) {
|
|
47
|
-
errorMessageParsed = undefined
|
|
48
|
-
}
|
|
49
|
-
if (errorMessageParsed) {
|
|
50
|
-
const messageTag = errorMessageParsed.error['.tag']
|
|
51
|
-
errorMessage = dropboxErrorMessages[messageTag] || messageTag
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return `Network response failure — ${response.status} (${
|
|
55
|
-
response.statusText
|
|
56
|
-
})${errorMessage ? ` (${errorMessage})` : ''}`
|
|
57
|
-
}
|
|
58
|
-
|
|
21
|
+
/**
|
|
22
|
+
* #stateModel DropboxOAuthInternetAccount
|
|
23
|
+
*/
|
|
59
24
|
const stateModelFactory = (
|
|
60
25
|
configSchema: DropboxOAuthInternetAccountConfigModel,
|
|
61
26
|
) => {
|
|
62
27
|
return baseModel(OAuthConfigSchema)
|
|
63
28
|
.named('DropboxOAuthInternetAccount')
|
|
64
29
|
.props({
|
|
30
|
+
/**
|
|
31
|
+
* #property
|
|
32
|
+
*/
|
|
65
33
|
type: types.literal('DropboxOAuthInternetAccount'),
|
|
34
|
+
/**
|
|
35
|
+
* #property
|
|
36
|
+
*/
|
|
66
37
|
configuration: ConfigurationReference(configSchema),
|
|
67
38
|
})
|
|
68
39
|
.views(() => ({
|
|
40
|
+
/**
|
|
41
|
+
* #getter
|
|
42
|
+
* The FileSelector icon for Dropbox
|
|
43
|
+
*/
|
|
69
44
|
get toggleContents() {
|
|
70
45
|
return <DropboxIcon />
|
|
71
46
|
},
|
|
47
|
+
/**
|
|
48
|
+
* #getter
|
|
49
|
+
*/
|
|
72
50
|
get selectorLabel() {
|
|
73
51
|
return 'Enter Dropbox share link'
|
|
74
52
|
},
|
|
75
53
|
}))
|
|
76
54
|
.actions(self => ({
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
init?: RequestInit,
|
|
83
|
-
): Promise<Response> => {
|
|
55
|
+
/**
|
|
56
|
+
* #method
|
|
57
|
+
*/
|
|
58
|
+
getFetcher(location?: UriLocation) {
|
|
59
|
+
return async (input: RequestInfo, init?: RequestInit) => {
|
|
84
60
|
const authToken = await self.getToken(location)
|
|
85
61
|
const newInit = self.addAuthHeaderToInit(
|
|
86
62
|
{ ...init, method: 'POST' },
|
|
@@ -95,12 +71,14 @@ const stateModelFactory = (
|
|
|
95
71
|
newInit,
|
|
96
72
|
)
|
|
97
73
|
if (!response.ok) {
|
|
98
|
-
|
|
99
|
-
throw new Error(message)
|
|
74
|
+
throw new Error(await getDescriptiveErrorMessage(response))
|
|
100
75
|
}
|
|
101
76
|
return response
|
|
102
77
|
}
|
|
103
78
|
},
|
|
79
|
+
/**
|
|
80
|
+
* #action
|
|
81
|
+
*/
|
|
104
82
|
async validateToken(
|
|
105
83
|
token: string,
|
|
106
84
|
location: UriLocation,
|
|
@@ -119,8 +97,7 @@ const stateModelFactory = (
|
|
|
119
97
|
},
|
|
120
98
|
)
|
|
121
99
|
if (!response.ok) {
|
|
122
|
-
const refreshToken =
|
|
123
|
-
self.hasRefreshToken && self.retrieveRefreshToken()
|
|
100
|
+
const refreshToken = self.retrieveRefreshToken()
|
|
124
101
|
if (refreshToken) {
|
|
125
102
|
self.removeRefreshToken()
|
|
126
103
|
const newToken = await self.exchangeRefreshForAccessToken(
|
|
@@ -128,8 +105,12 @@ const stateModelFactory = (
|
|
|
128
105
|
)
|
|
129
106
|
return this.validateToken(newToken, location)
|
|
130
107
|
}
|
|
131
|
-
|
|
132
|
-
|
|
108
|
+
throw new Error(
|
|
109
|
+
await getDescriptiveErrorMessage(
|
|
110
|
+
response,
|
|
111
|
+
'Token could not be validated',
|
|
112
|
+
),
|
|
113
|
+
)
|
|
133
114
|
}
|
|
134
115
|
return token
|
|
135
116
|
},
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getResponseError } from '../util'
|
|
2
|
+
|
|
3
|
+
interface DropboxError {
|
|
4
|
+
error_summary: string
|
|
5
|
+
error: {
|
|
6
|
+
'.tag': string
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Error messages from
|
|
12
|
+
* https://www.dropbox.com/developers/documentation/http/documentation#sharing-get_shared_link_file
|
|
13
|
+
* */
|
|
14
|
+
const dropboxErrorMessages: Record<string, string | undefined> = {
|
|
15
|
+
shared_link_not_found: "The shared link wasn't found.",
|
|
16
|
+
shared_link_access_denied:
|
|
17
|
+
'The caller is not allowed to access this shared link.',
|
|
18
|
+
unsupported_link_type:
|
|
19
|
+
'This type of link is not supported; use files/export instead.',
|
|
20
|
+
shared_link_is_directory: 'Directories cannot be retrieved by this endpoint.',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function getDescriptiveErrorMessage(
|
|
24
|
+
response: Response,
|
|
25
|
+
reason?: string,
|
|
26
|
+
) {
|
|
27
|
+
let errorMessage = ''
|
|
28
|
+
try {
|
|
29
|
+
const err = JSON.parse(await response.text()) as DropboxError
|
|
30
|
+
const tag = err.error['.tag']
|
|
31
|
+
errorMessage = dropboxErrorMessages[tag] || tag
|
|
32
|
+
} catch (error) {
|
|
33
|
+
/* do nothing */
|
|
34
|
+
}
|
|
35
|
+
return getResponseError({ response, reason, statusText: errorMessage })
|
|
36
|
+
}
|
|
@@ -12,46 +12,44 @@ export const ExternalTokenEntryForm = ({
|
|
|
12
12
|
const [token, setToken] = useState('')
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</Dialog>
|
|
55
|
-
</>
|
|
15
|
+
<Dialog
|
|
16
|
+
open
|
|
17
|
+
maxWidth="xl"
|
|
18
|
+
data-testid="externalToken-form"
|
|
19
|
+
title={`Enter token for ${internetAccountId}`}
|
|
20
|
+
>
|
|
21
|
+
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
22
|
+
<TextField
|
|
23
|
+
required
|
|
24
|
+
label="Enter Token"
|
|
25
|
+
variant="outlined"
|
|
26
|
+
inputProps={{ 'data-testid': 'entry-externalToken' }}
|
|
27
|
+
onChange={event => setToken(event.target.value)}
|
|
28
|
+
margin="dense"
|
|
29
|
+
/>
|
|
30
|
+
</DialogContent>
|
|
31
|
+
<DialogActions>
|
|
32
|
+
<Button
|
|
33
|
+
variant="contained"
|
|
34
|
+
color="primary"
|
|
35
|
+
type="submit"
|
|
36
|
+
disabled={!token}
|
|
37
|
+
onClick={() => {
|
|
38
|
+
if (token) {
|
|
39
|
+
handleClose(token)
|
|
40
|
+
}
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
Add
|
|
44
|
+
</Button>
|
|
45
|
+
<Button
|
|
46
|
+
variant="contained"
|
|
47
|
+
color="secondary"
|
|
48
|
+
onClick={() => handleClose()}
|
|
49
|
+
>
|
|
50
|
+
Cancel
|
|
51
|
+
</Button>
|
|
52
|
+
</DialogActions>
|
|
53
|
+
</Dialog>
|
|
56
54
|
)
|
|
57
55
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RemoteFileWithRangeCache } from '@jbrowse/core/util/io'
|
|
2
|
+
import {
|
|
3
|
+
FilehandleOptions,
|
|
4
|
+
Stats,
|
|
5
|
+
PolyfilledResponse,
|
|
6
|
+
} from 'generic-filehandle'
|
|
7
|
+
|
|
8
|
+
export interface RequestInitWithMetadata extends RequestInit {
|
|
9
|
+
metadataOnly?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface GoogleDriveFilehandleOptions extends FilehandleOptions {
|
|
13
|
+
fetch(
|
|
14
|
+
input: RequestInfo,
|
|
15
|
+
opts?: RequestInitWithMetadata,
|
|
16
|
+
): Promise<PolyfilledResponse>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class GoogleDriveFile extends RemoteFileWithRangeCache {
|
|
20
|
+
private statsPromise: Promise<{ size: number }>
|
|
21
|
+
constructor(source: string, opts: GoogleDriveFilehandleOptions) {
|
|
22
|
+
super(source, opts)
|
|
23
|
+
this.statsPromise = this.fetch(source, {
|
|
24
|
+
metadataOnly: true,
|
|
25
|
+
}).then((response: Response) => response.json())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async fetch(
|
|
29
|
+
input: RequestInfo,
|
|
30
|
+
opts?: RequestInitWithMetadata,
|
|
31
|
+
): Promise<PolyfilledResponse> {
|
|
32
|
+
return super.fetch(input, opts)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async stat(): Promise<Stats> {
|
|
36
|
+
return this.statsPromise
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,66 +1,20 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ConfigurationReference } from '@jbrowse/core/configuration'
|
|
3
3
|
import { Instance, types } from 'mobx-state-tree'
|
|
4
|
-
import { RemoteFileWithRangeCache } from '@jbrowse/core/util/io'
|
|
5
4
|
import { UriLocation } from '@jbrowse/core/util/types'
|
|
6
5
|
import { SvgIconProps, SvgIcon } from '@mui/material'
|
|
7
|
-
import {
|
|
8
|
-
FilehandleOptions,
|
|
9
|
-
Stats,
|
|
10
|
-
PolyfilledResponse,
|
|
11
|
-
} from 'generic-filehandle'
|
|
12
6
|
|
|
13
7
|
// locals
|
|
14
8
|
import { GoogleDriveOAuthInternetAccountConfigModel } from './configSchema'
|
|
15
9
|
import baseModel from '../OAuthModel/model'
|
|
16
10
|
import { configSchema as OAuthConfigSchema } from '../OAuthModel'
|
|
11
|
+
import { getDescriptiveErrorMessage } from './util'
|
|
12
|
+
import { GoogleDriveFile } from './GoogleDriveFilehandle'
|
|
17
13
|
|
|
18
14
|
export interface RequestInitWithMetadata extends RequestInit {
|
|
19
15
|
metadataOnly?: boolean
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
interface GoogleDriveFilehandleOptions extends FilehandleOptions {
|
|
23
|
-
fetch(
|
|
24
|
-
input: RequestInfo,
|
|
25
|
-
opts?: RequestInitWithMetadata,
|
|
26
|
-
): Promise<PolyfilledResponse>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface GoogleDriveError {
|
|
30
|
-
error: {
|
|
31
|
-
errors: {
|
|
32
|
-
domain: string
|
|
33
|
-
reason: string
|
|
34
|
-
message: string
|
|
35
|
-
locationType?: string
|
|
36
|
-
location?: string
|
|
37
|
-
}[]
|
|
38
|
-
code: number
|
|
39
|
-
message: string
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class GoogleDriveFile extends RemoteFileWithRangeCache {
|
|
44
|
-
private statsPromise: Promise<{ size: number }>
|
|
45
|
-
constructor(source: string, opts: GoogleDriveFilehandleOptions) {
|
|
46
|
-
super(source, opts)
|
|
47
|
-
this.statsPromise = this.fetch(source, {
|
|
48
|
-
metadataOnly: true,
|
|
49
|
-
}).then((response: Response) => response.json())
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async fetch(
|
|
53
|
-
input: RequestInfo,
|
|
54
|
-
opts?: RequestInitWithMetadata,
|
|
55
|
-
): Promise<PolyfilledResponse> {
|
|
56
|
-
return super.fetch(input, opts)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async stat(): Promise<Stats> {
|
|
60
|
-
return this.statsPromise
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
18
|
function GoogleDriveIcon(props: SvgIconProps) {
|
|
65
19
|
return (
|
|
66
20
|
<SvgIcon {...props}>
|
|
@@ -69,58 +23,51 @@ function GoogleDriveIcon(props: SvgIconProps) {
|
|
|
69
23
|
)
|
|
70
24
|
}
|
|
71
25
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
errorMessage = await response.text()
|
|
76
|
-
} catch (error) {
|
|
77
|
-
errorMessage = ''
|
|
78
|
-
}
|
|
79
|
-
if (errorMessage) {
|
|
80
|
-
let errorMessageParsed: GoogleDriveError | undefined
|
|
81
|
-
try {
|
|
82
|
-
errorMessageParsed = JSON.parse(errorMessage)
|
|
83
|
-
} catch (error) {
|
|
84
|
-
errorMessageParsed = undefined
|
|
85
|
-
}
|
|
86
|
-
if (errorMessageParsed) {
|
|
87
|
-
errorMessage = errorMessageParsed.error.message
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return `Network response failure — ${response.status} (${
|
|
91
|
-
response.statusText
|
|
92
|
-
})${errorMessage ? ` (${errorMessage})` : ''}`
|
|
26
|
+
function getUri(str: string) {
|
|
27
|
+
const urlId = str.match(/[-\w]{25,}/)
|
|
28
|
+
return `https://www.googleapis.com/drive/v3/files/${urlId}`
|
|
93
29
|
}
|
|
94
30
|
|
|
95
|
-
|
|
31
|
+
/**
|
|
32
|
+
* #stateModel GoogleDriveOAuthInternetAccount
|
|
33
|
+
*/
|
|
34
|
+
export default function stateModelFactory(
|
|
96
35
|
configSchema: GoogleDriveOAuthInternetAccountConfigModel,
|
|
97
|
-
)
|
|
36
|
+
) {
|
|
98
37
|
return baseModel(OAuthConfigSchema)
|
|
99
38
|
.named('GoogleDriveOAuthInternetAccount')
|
|
100
39
|
.props({
|
|
40
|
+
/**
|
|
41
|
+
* #property
|
|
42
|
+
*/
|
|
101
43
|
type: types.literal('GoogleDriveOAuthInternetAccount'),
|
|
44
|
+
/**
|
|
45
|
+
* #property
|
|
46
|
+
*/
|
|
102
47
|
configuration: ConfigurationReference(configSchema),
|
|
103
48
|
})
|
|
104
49
|
.views(() => ({
|
|
50
|
+
/**
|
|
51
|
+
* #getter
|
|
52
|
+
* The FileSelector icon for Google drive
|
|
53
|
+
*/
|
|
105
54
|
get toggleContents() {
|
|
106
55
|
return <GoogleDriveIcon />
|
|
107
56
|
},
|
|
57
|
+
/**
|
|
58
|
+
* #getter
|
|
59
|
+
*/
|
|
108
60
|
get selectorLabel() {
|
|
109
61
|
return 'Enter Google Drive share link'
|
|
110
62
|
},
|
|
111
63
|
}))
|
|
112
64
|
.actions(self => ({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
): Promise<Response> => {
|
|
120
|
-
const urlId = String(input).match(/[-\w]{25,}/)
|
|
121
|
-
const driveUrl = new URL(
|
|
122
|
-
`https://www.googleapis.com/drive/v3/files/${urlId}`,
|
|
123
|
-
)
|
|
65
|
+
/**
|
|
66
|
+
* #method
|
|
67
|
+
*/
|
|
68
|
+
getFetcher(location?: UriLocation) {
|
|
69
|
+
return async (input: RequestInfo, init?: RequestInitWithMetadata) => {
|
|
70
|
+
const driveUrl = new URL(getUri(String(input)))
|
|
124
71
|
const searchParams = new URLSearchParams()
|
|
125
72
|
if (init?.metadataOnly) {
|
|
126
73
|
searchParams.append('fields', 'size')
|
|
@@ -129,46 +76,49 @@ const stateModelFactory = (
|
|
|
129
76
|
}
|
|
130
77
|
driveUrl.search = searchParams.toString()
|
|
131
78
|
const authToken = await self.getToken(location)
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
79
|
+
const response = await fetch(
|
|
80
|
+
driveUrl,
|
|
81
|
+
self.addAuthHeaderToInit(
|
|
82
|
+
{ ...init, method: 'GET', credentials: 'same-origin' },
|
|
83
|
+
authToken,
|
|
84
|
+
),
|
|
135
85
|
)
|
|
136
|
-
const response = await fetch(driveUrl.toString(), newInit)
|
|
137
86
|
if (!response.ok) {
|
|
138
|
-
|
|
139
|
-
throw new Error(message)
|
|
87
|
+
throw new Error(await getDescriptiveErrorMessage(response))
|
|
140
88
|
}
|
|
141
89
|
return response
|
|
142
90
|
}
|
|
143
91
|
},
|
|
92
|
+
/**
|
|
93
|
+
* #method
|
|
94
|
+
*/
|
|
144
95
|
openLocation(location: UriLocation) {
|
|
145
96
|
return new GoogleDriveFile(location.uri, {
|
|
146
97
|
fetch: this.getFetcher(location),
|
|
147
98
|
})
|
|
148
99
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
headers: {
|
|
158
|
-
Authorization: `Bearer ${token}`,
|
|
159
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
160
|
-
},
|
|
100
|
+
/**
|
|
101
|
+
* #action
|
|
102
|
+
*/
|
|
103
|
+
async validateToken(token: string, location: UriLocation) {
|
|
104
|
+
const response = await fetch(getUri(location.uri), {
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${token}`,
|
|
107
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
161
108
|
},
|
|
162
|
-
)
|
|
109
|
+
})
|
|
163
110
|
if (!response.ok) {
|
|
164
|
-
|
|
165
|
-
|
|
111
|
+
throw new Error(
|
|
112
|
+
await getDescriptiveErrorMessage(
|
|
113
|
+
response,
|
|
114
|
+
'Token could not be validated',
|
|
115
|
+
),
|
|
116
|
+
)
|
|
166
117
|
}
|
|
167
118
|
return token
|
|
168
119
|
},
|
|
169
120
|
}))
|
|
170
121
|
}
|
|
171
122
|
|
|
172
|
-
export default stateModelFactory
|
|
173
123
|
export type GoogleDriveOAuthStateModel = ReturnType<typeof stateModelFactory>
|
|
174
124
|
export type GoogleDriveOAuthModel = Instance<GoogleDriveOAuthStateModel>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getResponseError } from '../util'
|
|
2
|
+
|
|
3
|
+
interface GoogleDriveError {
|
|
4
|
+
error: {
|
|
5
|
+
errors: {
|
|
6
|
+
domain: string
|
|
7
|
+
reason: string
|
|
8
|
+
message: string
|
|
9
|
+
locationType?: string
|
|
10
|
+
location?: string
|
|
11
|
+
}[]
|
|
12
|
+
code: number
|
|
13
|
+
message: string
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getDescriptiveErrorMessage(
|
|
18
|
+
response: Response,
|
|
19
|
+
reason?: string,
|
|
20
|
+
) {
|
|
21
|
+
let errorMessage = ''
|
|
22
|
+
try {
|
|
23
|
+
const err = JSON.parse(await response.text()) as GoogleDriveError
|
|
24
|
+
errorMessage = err.error.message
|
|
25
|
+
} catch (error) {
|
|
26
|
+
/* do nothing */
|
|
27
|
+
}
|
|
28
|
+
return getResponseError({ response, reason, statusText: errorMessage })
|
|
29
|
+
}
|