Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
46a31363
Commit
46a31363
authored
Apr 22, 2021
by
Robert Knight
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Convert `api` service to an ES class
Part of
https://github.com/hypothesis/client/issues/3298
parent
4471182d
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
63 additions
and
54 deletions
+63
-54
ModerationBanner.js
src/sidebar/components/ModerationBanner.js
+1
-1
StreamView.js
src/sidebar/components/StreamView.js
+1
-1
index.js
src/sidebar/index.js
+2
-2
annotations.js
src/sidebar/services/annotations.js
+1
-1
api.js
src/sidebar/services/api.js
+52
-45
groups.js
src/sidebar/services/groups.js
+1
-0
load-annotations.js
src/sidebar/services/load-annotations.js
+1
-1
session.js
src/sidebar/services/session.js
+1
-0
api-test.js
src/sidebar/services/test/api-test.js
+3
-3
No files found.
src/sidebar/components/ModerationBanner.js
View file @
46a31363
...
...
@@ -13,7 +13,7 @@ import { withServices } from '../service-context';
* @prop {Annotation} annotation -
* The annotation object for this banner. This contains state about the flag count
* or its hidden value.
* @prop {
Object} api - Injected service
* @prop {
import('../services/api').APIService} api
* @prop {import('../services/toast-messenger').ToastMessengerService} toastMessenger
*/
...
...
src/sidebar/components/StreamView.js
View file @
46a31363
...
...
@@ -9,7 +9,7 @@ import ThreadList from './ThreadList';
/**
* @typedef StreamViewProps
* @prop {
ReturnType<import('../services/api').default>
} api
* @prop {
import('../services/api').APIService
} api
* @prop {import('../services/toast-messenger').ToastMessengerService} toastMessenger
*/
...
...
src/sidebar/index.js
View file @
46a31363
...
...
@@ -107,7 +107,7 @@ import { ServiceContext } from './service-context';
import
bridgeService
from
'../shared/bridge'
;
import
{
AnnotationsService
}
from
'./services/annotations'
;
import
apiService
from
'./services/api'
;
import
{
APIService
}
from
'./services/api'
;
import
{
APIRoutesService
}
from
'./services/api-routes'
;
import
{
AuthService
}
from
'./services/auth'
;
import
{
AutosaveService
}
from
'./services/autosave'
;
...
...
@@ -144,7 +144,7 @@ function startApp(config, appEl) {
// Register services.
container
.
register
(
'annotationsService'
,
AnnotationsService
)
.
register
(
'api'
,
api
Service
)
.
register
(
'api'
,
API
Service
)
.
register
(
'apiRoutes'
,
APIRoutesService
)
.
register
(
'auth'
,
AuthService
)
.
register
(
'autosaveService'
,
AutosaveService
)
...
...
src/sidebar/services/annotations.js
View file @
46a31363
...
...
@@ -16,7 +16,7 @@ import { generateHexString } from '../util/random';
// @inject
export
class
AnnotationsService
{
/**
* @param {
ReturnType<import('./api').default>
} api
* @param {
import('./api').APIService
} api
* @param {import('../store').SidebarStore} store
*/
constructor
(
api
,
store
)
{
...
...
src/sidebar/services/api.js
View file @
46a31363
...
...
@@ -14,7 +14,7 @@ import { replaceURLParams } from '../util/url';
*/
function
translateResponseToError
(
response
,
data
)
{
let
message
=
response
.
status
+
' '
+
response
.
statusText
;
if
(
data
&&
data
.
reason
)
{
if
(
data
?
.
reason
)
{
message
=
message
+
': '
+
data
.
reason
;
}
return
new
Error
(
message
);
...
...
@@ -24,7 +24,7 @@ function translateResponseToError(response, data) {
* Return a shallow clone of `obj` with all client-only properties removed.
* Client-only properties are marked by a '$' prefix.
*
* @param {
O
bject} obj
* @param {
o
bject} obj
*/
function
stripInternalProperties
(
obj
)
{
const
result
=
{};
...
...
@@ -61,7 +61,7 @@ function stripInternalProperties(obj) {
*
* @callback APICallFunction
* @param {Record<string, any>} params - A map of URL and query string parameters to include with the request.
* @param {
O
bject} [data] - The body of the request.
* @param {
o
bject} [data] - The body of the request.
* @param {APICallOptions} [options]
* @return {Promise<any|APIResponse>}
*/
...
...
@@ -69,7 +69,7 @@ function stripInternalProperties(obj) {
/**
* Callbacks invoked at various points during an API call to get an access token etc.
*
* @typedef
{Object}
APIMethodCallbacks
* @typedef APIMethodCallbacks
* @prop {() => Promise<string|null>} getAccessToken -
* Function which acquires a valid access token for making an API request.
* @prop {() => string|null} getClientId -
...
...
@@ -193,44 +193,41 @@ function createAPICall(
* });
* ```
*
* This service handles authenticated calls to the API, using the `auth` service
* to get auth tokens. The URLs for API endpoints are fetched from the `/api`
* endpoint, a responsibility delegated to the `apiRoutes` service which does
* not use authentication.
*
* This service makes authenticated calls to the API, using `AuthService`
* to get auth tokens. The URLs for API endpoints are provided by the `APIRoutesService`
* service.
*/
// @inject
export
class
APIService
{
/**
* @param {import('./api-routes').APIRoutesService} apiRoutes
* @param {import('./auth').AuthService} auth
* @param {import('../store').SidebarStore} store
*/
// @inject
export
default
function
api
(
apiRoutes
,
auth
,
store
)
{
constructor
(
apiRoutes
,
auth
,
store
)
{
const
links
=
apiRoutes
.
routes
();
let
clientId
=
null
;
const
getClientId
=
()
=>
clientId
;
/**
* Client session identifier included with requests. Used by the backend
* to associate API requests with WebSocket connections from the same client.
*
* @type {string|null}
*/
this
.
_clientId
=
null
;
function
apiCall
(
route
)
{
return
createAPICall
(
links
,
route
,
{
const
getClientId
=
()
=>
this
.
_clientId
;
/** @param {string} route */
const
apiCall
=
route
=>
createAPICall
(
links
,
route
,
{
getAccessToken
:
auth
.
getAccessToken
,
getClientId
,
onRequestStarted
:
store
.
apiRequestStarted
,
onRequestFinished
:
store
.
apiRequestFinished
,
});
}
return
{
/**
* Set the "client ID" sent with API requests.
*
* This is a per-session unique ID which the client sends with REST API
* requests and in the configuration for the real-time API to prevent the
* client from receiving real-time notifications about its own actions.
*/
setClientId
(
clientId_
)
{
clientId
=
clientId_
;
},
search
:
apiCall
(
'search'
),
annotation
:
{
this
.
search
=
apiCall
(
'search'
);
this
.
annotation
=
{
create
:
apiCall
(
'annotation.create'
),
delete
:
apiCall
(
'annotation.delete'
),
get
:
apiCall
(
'annotation.read'
),
...
...
@@ -238,25 +235,35 @@ export default function api(apiRoutes, auth, store) {
flag
:
apiCall
(
'annotation.flag'
),
hide
:
apiCall
(
'annotation.hide'
),
unhide
:
apiCall
(
'annotation.unhide'
),
}
,
group
:
{
}
;
this
.
group
=
{
member
:
{
delete
:
apiCall
(
'group.member.delete'
),
},
read
:
apiCall
(
'group.read'
),
}
,
groups
:
{
}
;
this
.
groups
=
{
list
:
apiCall
(
'groups.read'
),
}
,
profile
:
{
}
;
this
.
profile
=
{
groups
:
{
read
:
apiCall
(
'profile.groups.read'
),
},
read
:
apiCall
(
'profile.read'
),
update
:
apiCall
(
'profile.update'
),
},
// The `links` endpoint is not included here. Clients should fetch these
// from the `apiRoutes` service.
};
}
/**
* Set the "client ID" sent with API requests.
*
* This is a per-session unique ID which the client sends with REST API
* requests and in the configuration for the real-time API to prevent the
* client from receiving real-time notifications about its own actions.
*
* @param {string} clientId
*/
setClientId
(
clientId
)
{
this
.
_clientId
=
clientId
;
}
}
src/sidebar/services/groups.js
View file @
46a31363
...
...
@@ -22,6 +22,7 @@ const DEFAULT_ORGANIZATION = {
/**
* @param {import('../store').SidebarStore} store
* @param {import('./api').APIService} api
* @param {import('./toast-messenger').ToastMessengerService} toastMessenger
* @param {import('./auth').AuthService} auth
*/
...
...
src/sidebar/services/load-annotations.js
View file @
46a31363
...
...
@@ -30,7 +30,7 @@ import { SearchClient } from '../search-client';
import
{
isReply
}
from
'../helpers/annotation-metadata'
;
/**
* @param {
ReturnType<import('./api').default>
} api
* @param {
import('./api').APIService
} api
* @param {import('../store').SidebarStore} store
* @param {import('./streamer').default} streamer
* @param {import('./stream-filter').StreamFilter} streamFilter
...
...
src/sidebar/services/session.js
View file @
46a31363
...
...
@@ -13,6 +13,7 @@ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
* Access to the current profile is exposed via the `state` property.
*
* @param {import('../store').SidebarStore} store
* @param {import('./api').APIService} api
* @param {import('./auth').AuthService} auth
* @param {import('./toast-messenger').ToastMessengerService} toastMessenger
* @inject
...
...
src/sidebar/services/test/api-test.js
View file @
46a31363
import
fetchMock
from
'fetch-mock'
;
import
apiFactory
from
'../api'
;
import
{
APIService
}
from
'../api'
;
// API route directory.
//
...
...
@@ -13,7 +13,7 @@ import apiFactory from '../api';
//
const
routes
=
require
(
'./api-index.json'
).
links
;
describe
(
'
sidebar.services.api'
,
function
()
{
describe
(
'
APIService'
,
()
=>
{
let
fakeAuth
;
let
fakeStore
;
let
api
;
...
...
@@ -72,7 +72,7 @@ describe('sidebar.services.api', function () {
apiRequestFinished
:
sinon
.
stub
(),
};
api
=
apiFactory
(
fakeApiRoutes
,
fakeAuth
,
fakeStore
);
api
=
new
APIService
(
fakeApiRoutes
,
fakeAuth
,
fakeStore
);
fetchMock
.
catch
(()
=>
{
throw
new
Error
(
'Unexpected `fetch` call'
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment