Commit 2ae8d153 authored by Robert Knight's avatar Robert Knight

Begin to add JSDoc types for our configuration objects

The client's "sidebar" and "annotator" applications query configuration
from various sources (the host page, the H backend / browser extension
build, added dynamically by the app).

This commit begins the process of adding a centralized place to document
the types of these configuration objects and making typechecked modules
in `src/sidebar/util` use them.
parent 487a1b43
...@@ -6,44 +6,13 @@ import { ...@@ -6,44 +6,13 @@ import {
toString, toString,
} from '../shared/type-coercions'; } from '../shared/type-coercions';
/** /** @typedef {import('../types/config').HostConfig} HostConfig */
* @typedef RequestConfigFromFrameOptions
* @prop {number} ancestorLevel
* @prop {string} origin
*/
/**
* Configuration for the client provided by the frame embedding it.
*
* User-facing documentation exists at
* https://h.readthedocs.io/projects/client/en/latest/publishers/config/
*
* @typedef Config
* @prop {string} [annotations] - Direct-linked annotation ID
* @prop {string} [group] - Direct-linked group ID
* @prop {string} [query] - Initial filter query
* @prop {string} [appType] - Method used to load the client
* @prop {boolean} [openSidebar] - Whether to open the sidebar on the initial load
* @prop {boolean} [showHighlights] - Whether to show highlights
* @prop {Object[]} [services] -
* Configuration for the annotation services that the client connects to
* @prop {Object} [branding] -
* Theme properties (fonts, colors etc.)
* @prop {boolean} [enableExperimentalNewNoteButton] -
* Whether to show the "New note" button on the "Page Notes" tab
* @prop {RequestConfigFromFrameOptions|string} [requestConfigFromFrame]
* Origin of the ancestor frame to request configuration from
* @prop {string} [theme]
* Name of the base theme to use.
* @prop {string} [usernameUrl]
* URL template for username links
*/
/** /**
* Return the app configuration specified by the frame embedding the Hypothesis * Return the app configuration specified by the frame embedding the Hypothesis
* client. * client.
* *
* @return {Config} * @return {HostConfig}
*/ */
export default function hostPageConfig(window) { export default function hostPageConfig(window) {
const configStr = window.location.hash.slice(1); const configStr = window.location.hash.slice(1);
......
/**
* @typedef {import('../types/config').HostConfig} HostConfig
* @typedef {import('../types/config').Service} Service
*/
/** /**
* Return the configuration for the annotation service which the client would retrieve * Return the configuration for the annotation service which the client would retrieve
* annotations from which may contain the authority, grantToken and icon. * annotations from which may contain the authority, grantToken and icon.
* *
* @param {Object} settings - The settings object which would contain the services array. * @param {HostConfig} settings
* @return {Service|null}
*/ */
export default function serviceConfig(settings) { export default function serviceConfig(settings) {
......
/**
* @typedef {import('../../types/config').HostConfig} HostConfig
*/
import serviceConfig from '../service-config'; import serviceConfig from '../service-config';
/** /**
* Is the sharing of annotations enabled? Check for any defined `serviceConfig`, * Is the sharing of annotations enabled? Check for any defined `serviceConfig`,
* but default to `true` if none found. * but default to `true` if none found.
* *
* @param {object} settings * @param {HostConfig} settings
* @return {boolean} * @return {boolean}
*/ */
export function sharingEnabled(settings) { export function sharingEnabled(settings) {
...@@ -35,7 +39,7 @@ export function shareURI(annotation) { ...@@ -35,7 +39,7 @@ export function shareURI(annotation) {
* and the annotation itself needs to have a sharing URI. * and the annotation itself needs to have a sharing URI.
* *
* @param {object} annotation * @param {object} annotation
* @param {object} settings * @param {HostConfig} settings
* @return {boolean} * @return {boolean}
*/ */
export function isShareable(annotation, settings) { export function isShareable(annotation, settings) {
......
...@@ -2,6 +2,11 @@ import getApiUrl from '../get-api-url'; ...@@ -2,6 +2,11 @@ import getApiUrl from '../get-api-url';
import hostConfig from '../host-config'; import hostConfig from '../host-config';
import * as postMessageJsonRpc from './postmessage-json-rpc'; import * as postMessageJsonRpc from './postmessage-json-rpc';
/**
* @typedef {import('../../types/config').SidebarConfig} SidebarConfig
* @typedef {import('../../types/config').MergedConfig} MergedConfig
*/
/** /**
* @deprecated * @deprecated
*/ */
...@@ -180,9 +185,9 @@ async function fetchGroupsAsync(config, rpcCall) { ...@@ -180,9 +185,9 @@ async function fetchGroupsAsync(config, rpcCall) {
* Legacy RPC with unknown parent - From a ancestor parent frame that passes it down via RPC. (deprecated) * Legacy RPC with unknown parent - From a ancestor parent frame that passes it down via RPC. (deprecated)
* RPC with known parent - From a ancestor parent frame that passes it down via RPC. * RPC with known parent - From a ancestor parent frame that passes it down via RPC.
* *
* @param {Object} appConfig - Settings rendered into `app.html` by the h service. * @param {SidebarConfig} appConfig
* @param {Window} window_ - Test seam. * @param {Window} window_ - Test seam.
* @return {Promise<Object>} - The merged settings. * @return {Promise<MergedConfig>} - The merged settings.
*/ */
export async function fetchConfig(appConfig, window_ = window) { export async function fetchConfig(appConfig, window_ = window) {
const hostPageConfig = hostConfig(window); const hostPageConfig = hostConfig(window);
......
/** @typedef {import('../../types/api').Group} Group */ /**
* @typedef {import('../../types/config').HostConfig} HostConfig
* @typedef {import('../../types/api').Group} Group
*/
import escapeStringRegexp from 'escape-string-regexp'; import escapeStringRegexp from 'escape-string-regexp';
import serviceConfig from '../service-config'; import serviceConfig from '../service-config';
...@@ -9,14 +12,15 @@ import serviceConfig from '../service-config'; ...@@ -9,14 +12,15 @@ import serviceConfig from '../service-config';
* explicitly disallowed in the service configuration of the * explicitly disallowed in the service configuration of the
* `settings` object. * `settings` object.
* *
* @param {object} settings * @param {HostConfig} settings
* @return {boolean} * @return {boolean}
*/ */
function allowLeavingGroups(settings) { function allowLeavingGroups(settings) {
const config = serviceConfig(settings) || {}; const config = serviceConfig(settings);
return typeof config.allowLeavingGroups === 'boolean' if (!config) {
? config.allowLeavingGroups return true;
: true; }
return !!config.allowLeavingGroups;
} }
/** /**
...@@ -28,7 +32,7 @@ function allowLeavingGroups(settings) { ...@@ -28,7 +32,7 @@ function allowLeavingGroups(settings) {
* @param {Group[]} userGroups - groups the user is a member of * @param {Group[]} userGroups - groups the user is a member of
* @param {Group[]} featuredGroups - all other groups, may include some duplicates from the userGroups * @param {Group[]} featuredGroups - all other groups, may include some duplicates from the userGroups
* @param {string} uri - uri of the current page * @param {string} uri - uri of the current page
* @param {object} settings - The settings object. * @param {HostConfig} settings
*/ */
export function combineGroups(userGroups, featuredGroups, uri, settings) { export function combineGroups(userGroups, featuredGroups, uri, settings) {
const worldGroup = featuredGroups.find(g => g.id === '__world__'); const worldGroup = featuredGroups.find(g => g.id === '__world__');
......
/**
* @typedef {import('../../types/config').MergedConfig} MergedConfig
*/
import serviceConfig from '../service-config'; import serviceConfig from '../service-config';
/** /**
...@@ -8,7 +12,7 @@ import serviceConfig from '../service-config'; ...@@ -8,7 +12,7 @@ import serviceConfig from '../service-config';
* *
* If no custom annotation services are configured then return `false`. * If no custom annotation services are configured then return `false`.
* *
* @param {Object} settings - the sidebar settings object * @param {MergedConfig} settings
* @return {boolean} * @return {boolean}
*/ */
export default function isThirdPartyService(settings) { export default function isThirdPartyService(settings) {
......
import serviceConfig from '../service-config'; import serviceConfig from '../service-config';
/** @typedef {import('../../types/api').Profile} Profile */ /**
* @typedef {import('../../types/config').HostConfig} HostConfig
* @typedef {import('../../types/api').Profile} Profile
*/
/** /**
* Returns true if the sidebar tutorial has to be shown to a user for a given session. * Returns true if the sidebar tutorial has to be shown to a user for a given session.
...@@ -28,14 +31,15 @@ export function shouldShowSidebarTutorial(sessionState) { ...@@ -28,14 +31,15 @@ export function shouldShowSidebarTutorial(sessionState) {
* *
* @param {boolean} isSidebar - is the app currently displayed in a sidebar? * @param {boolean} isSidebar - is the app currently displayed in a sidebar?
* @param {Profile} profile - User profile returned from the API * @param {Profile} profile - User profile returned from the API
* @param {Object} settings - app configuration/settings * @param {HostConfig} settings
* @return {boolean} - Tutorial panel should be displayed automatically * @return {boolean} - Tutorial panel should be displayed automatically
*/ */
export function shouldAutoDisplayTutorial(isSidebar, profile, settings) { export function shouldAutoDisplayTutorial(isSidebar, profile, settings) {
const shouldShowBasedOnProfile = const shouldShowBasedOnProfile =
typeof profile.preferences === 'object' && typeof profile.preferences === 'object' &&
!!profile.preferences.show_sidebar_tutorial; !!profile.preferences.show_sidebar_tutorial;
const service = serviceConfig(settings) || {};
const service = serviceConfig(settings) || { onHelpRequestProvided: false };
return ( return (
isSidebar && !service.onHelpRequestProvided && shouldShowBasedOnProfile isSidebar && !service.onHelpRequestProvided && shouldShowBasedOnProfile
); );
......
/**
* Configuration for an annotation service.
*
* See https://h.readthedocs.io/projects/client/en/latest/publishers/config/#cmdoption-arg-services
*
* The `onXXX` functions may be set by the embedder of the client. The
* `onXXXProvided` booleans are correspondingly set in the annotator if a
* particular function is provided.
*
* @typedef Service
* @prop {string} apiUrl
* @prop {string} authority
* @prop {string} grantToken
* @prop {boolean} [allowLeavingGroups]
* @prop {boolean} [enableShareLinks]
* @prop {Function} [onLoginRequest]
* @prop {boolean} [onLoginRequestProvided]
* @prop {Function} [onLogoutRequest]
* @prop {boolean} [onLogoutRequestProvided]
* @prop {Function} [onSignupRequest]
* @prop {boolean} [onSignupRequestProvided]
* @prop {Function} [onProfileRequest]
* @prop {boolean} [onProfileRequestProvided]
* @prop {Function} [onHelpRequest]
* @prop {boolean} [onHelpRequestProvided]
*/
/**
* Configuration for the Sentry crash-reporting service.
*
* @typedef SentryConfig
* @prop {string} dsn
* @prop {string} environment
*/
/**
* Configuration for the sidebar app set by the Hypothesis backend ("h")
* or baked into the sidebar app at build time (in the browser extension).
*
* See `h.views.client` in the "h" application.
*
* @typedef SidebarConfig
* @prop {string} apiUrl
* @prop {string} authDomain
* @prop {string} [googleAnalytics]
* @prop {string} oauthClientId
* @prop {string[]} rpcAllowedOrigins
* @prop {SentryConfig} [sentry]
* @prop {string} [websocketUrl]
*/
/**
* @typedef RequestConfigFromFrameOptions
* @prop {number} ancestorLevel
* @prop {string} origin
*/
/**
* Configuration set by the embedder of the client and used by the sidebar.
*
* This is the subset of keys from
* https://h.readthedocs.io/projects/client/en/latest/publishers/config/ which
* excludes any keys used only by the "annotator" part of the application.
*
* @typedef HostConfig
* @prop {string} [annotations] - Direct-linked annotation ID
* @prop {string} [group] - Direct-linked group ID
* @prop {string} [query] - Initial filter query
* @prop {string} [appType] - Method used to load the client
* @prop {boolean} [openSidebar] - Whether to open the sidebar on the initial load
* @prop {boolean} [showHighlights] - Whether to show highlights
* @prop {Object} [branding] -
* Theme properties (fonts, colors etc.)
* @prop {boolean} [enableExperimentalNewNoteButton] -
* Whether to show the "New note" button on the "Page Notes" tab
* @prop {RequestConfigFromFrameOptions|string} [requestConfigFromFrame]
* Origin of the ancestor frame to request configuration from
* @prop {Service[]} [services] -
* Configuration for the annotation services that the client connects to
* @prop {string} [theme]
* Name of the base theme to use.
* @prop {string} [usernameUrl]
* URL template for username links
*/
/**
* The `settings` object used in the sidebar app that is a result of merging
* (filtered and validated) configuration from the host page with configuration
* from h / the browser extension.
*
* @typedef {HostConfig & SidebarConfig} MergedConfig
*/
// Make TypeScript treat this file as a module.
export const unused = {};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment