diff --git a/src/annotator/annotation-counts.js b/src/annotator/annotation-counts.js index 0be3ef4749e..f8fbd5fb3b4 100644 --- a/src/annotator/annotation-counts.js +++ b/src/annotator/annotation-counts.js @@ -1,4 +1,4 @@ -import events from '../shared/bridge-events'; +import { sidebarToHostEvents } from '../shared/bridge-events'; const ANNOTATION_COUNT_ATTR = 'data-hypothesis-annotation-count'; @@ -11,8 +11,12 @@ const ANNOTATION_COUNT_ATTR = 'data-hypothesis-annotation-count'; * display annotation count. * @param {import('../shared/bridge').Bridge} bridge - Channel for host-sidebar communication */ + export function annotationCounts(rootEl, bridge) { - bridge.on(events.PUBLIC_ANNOTATION_COUNT_CHANGED, updateAnnotationCountElems); + bridge.on( + sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, + updateAnnotationCountElems + ); function updateAnnotationCountElems(newCount) { const elems = rootEl.querySelectorAll('[' + ANNOTATION_COUNT_ATTR + ']'); diff --git a/src/annotator/annotation-sync.js b/src/annotator/annotation-sync.js index f8a517f1f3e..a92bc45ce84 100644 --- a/src/annotator/annotation-sync.js +++ b/src/annotator/annotation-sync.js @@ -1,3 +1,8 @@ +import { + guestToSidebarEvents, + sidebarToGuestEvents, +} from '../shared/bridge-events'; + /** * @typedef {import('../shared/bridge').Bridge} Bridge * @typedef {import('../types/annotator').AnnotationData} AnnotationData @@ -43,7 +48,7 @@ export class AnnotationSync { this.destroyed = false; // Relay events from the sidebar to the rest of the annotator. - this.bridge.on('deleteAnnotation', (body, callback) => { + this.bridge.on(sidebarToGuestEvents.DELETE_ANNOTATION, (body, callback) => { if (this.destroyed) { callback(null); return; @@ -55,22 +60,28 @@ export class AnnotationSync { callback(null); }); - this.bridge.on('loadAnnotations', (bodies, callback) => { - if (this.destroyed) { + this.bridge.on( + sidebarToGuestEvents.LOAD_ANNOTATIONS, + (bodies, callback) => { + if (this.destroyed) { + callback(null); + return; + } + const annotations = bodies.map(body => this._parse(body)); + this._emitter.publish('annotationsLoaded', annotations); callback(null); - return; } - const annotations = bodies.map(body => this._parse(body)); - this._emitter.publish('annotationsLoaded', annotations); - callback(null); - }); + ); // Relay events from annotator to sidebar. this._emitter.subscribe('beforeAnnotationCreated', annotation => { if (annotation.$tag) { return; } - this.bridge.call('beforeCreateAnnotation', this._format(annotation)); + this.bridge.call( + guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, + this._format(annotation) + ); }); } @@ -88,7 +99,7 @@ export class AnnotationSync { } this.bridge.call( - 'sync', + guestToSidebarEvents.SYNC, annotations.map(ann => this._format(ann)) ); } diff --git a/src/annotator/features.js b/src/annotator/features.js index ba0b3554418..8bb4a305b5e 100644 --- a/src/annotator/features.js +++ b/src/annotator/features.js @@ -1,4 +1,4 @@ -import events from '../shared/bridge-events'; +import { sidebarToHostEvents } from '../shared/bridge-events'; import warnOnce from '../shared/warn-once'; let _features = {}; @@ -12,7 +12,7 @@ export const features = { * @param {import('../shared/bridge').Bridge} bridge - Channel for host-sidebar communication */ init: function (bridge) { - bridge.on(events.FEATURE_FLAGS_UPDATED, _set); + bridge.on(sidebarToHostEvents.FEATURE_FLAGS_UPDATED, _set); }, reset: function () { diff --git a/src/annotator/guest.js b/src/annotator/guest.js index 3fd6d35c5fb..f614040eb39 100644 --- a/src/annotator/guest.js +++ b/src/annotator/guest.js @@ -1,3 +1,7 @@ +import { + guestToSidebarEvents, + sidebarToGuestEvents, +} from '../shared/bridge-events'; import { Bridge } from '../shared/bridge'; import { ListenerCollection } from '../shared/listener-collection'; @@ -221,7 +225,7 @@ export default class Guest { // Don't hide the sidebar if the event comes from an element that contains a highlight return; } - this._bridge.call('closeSidebar'); + this._bridge.call(guestToSidebarEvents.CLOSE_SIDEBAR); }; this._listeners.add(this.element, 'mouseup', event => { @@ -310,7 +314,7 @@ export default class Guest { _connectSidebarEvents() { // Handlers for events sent when user hovers or clicks on an annotation card // in the sidebar. - this._bridge.on('focusAnnotations', (tags = []) => { + this._bridge.on(sidebarToGuestEvents.FOCUS_ANNOTATIONS, (tags = []) => { this._focusedAnnotations.clear(); tags.forEach(tag => this._focusedAnnotations.add(tag)); @@ -322,7 +326,7 @@ export default class Guest { } }); - this._bridge.on('scrollToAnnotation', tag => { + this._bridge.on(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, tag => { const anchor = this.anchors.find(a => a.annotation.$tag === tag); if (!anchor?.highlights) { return; @@ -348,16 +352,19 @@ export default class Guest { }); // Handler for when sidebar requests metadata for the current document - this._bridge.on('getDocumentInfo', cb => { + this._bridge.on(sidebarToGuestEvents.GET_DOCUMENT_INFO, cb => { this.getDocumentInfo() .then(info => cb(null, info)) .catch(reason => cb(reason)); }); // Handler for controls on the sidebar - this._bridge.on('setVisibleHighlights', showHighlights => { - this.setVisibleHighlights(showHighlights); - }); + this._bridge.on( + sidebarToGuestEvents.SET_VISIBLE_HIGHLIGHTS, + showHighlights => { + this.setVisibleHighlights(showHighlights); + } + ); } destroy() { @@ -578,7 +585,7 @@ export default class Guest { this.anchor(annotation); if (!annotation.$highlight) { - this._bridge.call('openSidebar'); + this._bridge.call(guestToSidebarEvents.OPEN_SIDEBAR); } return annotation; @@ -592,7 +599,7 @@ export default class Guest { */ _focusAnnotations(annotations) { const tags = annotations.map(a => a.$tag); - this._bridge.call('focusAnnotations', tags); + this._bridge.call(guestToSidebarEvents.FOCUS_ANNOTATIONS, tags); } /** @@ -643,11 +650,11 @@ export default class Guest { selectAnnotations(annotations, toggle = false) { const tags = annotations.map(a => a.$tag); if (toggle) { - this._bridge.call('toggleAnnotationSelection', tags); + this._bridge.call(guestToSidebarEvents.TOGGLE_ANNOTATION_SELECTION, tags); } else { - this._bridge.call('showAnnotations', tags); + this._bridge.call(guestToSidebarEvents.SHOW_ANNOTATIONS, tags); } - this._bridge.call('openSidebar'); + this._bridge.call(guestToSidebarEvents.OPEN_SIDEBAR); } /** diff --git a/src/annotator/sidebar.js b/src/annotator/sidebar.js index fa77745b9b8..0665b8ea2cf 100644 --- a/src/annotator/sidebar.js +++ b/src/annotator/sidebar.js @@ -1,7 +1,10 @@ import Hammer from 'hammerjs'; import { Bridge } from '../shared/bridge'; -import events from '../shared/bridge-events'; +import { + hostToSidebarEvents, + sidebarToHostEvents, +} from '../shared/bridge-events'; import { ListenerCollection } from '../shared/listener-collection'; import { annotationCounts } from './annotation-counts'; @@ -217,7 +220,10 @@ export default class Sidebar { this._listeners.add(window, 'message', event => { const { data } = /** @type {MessageEvent} */ (event); if (data?.type === 'hypothesisGuestUnloaded') { - this._sidebarRPC.call('destroyFrame', data.frameIdentifier); + this._sidebarRPC.call( + hostToSidebarEvents.DESTROY_FRAME, + data.frameIdentifier + ); } }); } @@ -239,25 +245,29 @@ export default class Sidebar { sidebarTrigger(document.body, () => this.open()); features.init(this._sidebarRPC); - this._sidebarRPC.on('openSidebar', () => this.open()); - this._sidebarRPC.on('closeSidebar', () => this.close()); + this._sidebarRPC.on(sidebarToHostEvents.OPEN_SIDEBAR, () => this.open()); + this._sidebarRPC.on(sidebarToHostEvents.CLOSE_SIDEBAR, () => this.close()); // Sidebar listens to the `openNotebook` event coming from the sidebar's // iframe and re-publishes it via the emitter to the Notebook - this._sidebarRPC.on('openNotebook', (/** @type {string} */ groupId) => { - this.hide(); - this._emitter.publish('openNotebook', groupId); - }); + this._sidebarRPC.on( + sidebarToHostEvents.OPEN_NOTEBOOK, + (/** @type {string} */ groupId) => { + this.hide(); + this._emitter.publish('openNotebook', groupId); + } + ); this._emitter.subscribe('closeNotebook', () => { this.show(); }); + /** @type {Array<[import('../shared/bridge-events').SidebarToHostEvent, function]>} */ const eventHandlers = [ - [events.LOGIN_REQUESTED, this.onLoginRequest], - [events.LOGOUT_REQUESTED, this.onLogoutRequest], - [events.SIGNUP_REQUESTED, this.onSignupRequest], - [events.PROFILE_REQUESTED, this.onProfileRequest], - [events.HELP_REQUESTED, this.onHelpRequest], + [sidebarToHostEvents.LOGIN_REQUESTED, this.onLoginRequest], + [sidebarToHostEvents.LOGOUT_REQUESTED, this.onLogoutRequest], + [sidebarToHostEvents.SIGNUP_REQUESTED, this.onSignupRequest], + [sidebarToHostEvents.PROFILE_REQUESTED, this.onProfileRequest], + [sidebarToHostEvents.HELP_REQUESTED, this.onHelpRequest], ]; eventHandlers.forEach(([event, handler]) => { if (handler) { @@ -439,7 +449,7 @@ export default class Sidebar { } open() { - this._sidebarRPC.call('sidebarOpened'); + this._sidebarRPC.call(hostToSidebarEvents.SIDEBAR_OPENED); this._emitter.publish('sidebarOpened'); if (this.iframeContainer) { @@ -478,7 +488,10 @@ export default class Sidebar { * @param {boolean} shouldShowHighlights */ setAllVisibleHighlights(shouldShowHighlights) { - this._sidebarRPC.call('setVisibleHighlights', shouldShowHighlights); + this._sidebarRPC.call( + hostToSidebarEvents.SET_VISIBLE_HIGHLIGHTS, + shouldShowHighlights + ); } /** diff --git a/src/annotator/test/annotation-counts-test.js b/src/annotator/test/annotation-counts-test.js index 1e7e20d41f0..61c50aebabd 100644 --- a/src/annotator/test/annotation-counts-test.js +++ b/src/annotator/test/annotation-counts-test.js @@ -1,3 +1,4 @@ +import { sidebarToHostEvents } from '../../shared/bridge-events'; import { annotationCounts } from '../annotation-counts'; describe('annotationCounts', () => { @@ -57,7 +58,7 @@ describe('annotationCounts', () => { const newCount = 10; annotationCounts(document.body, fakeCrossFrame); - emitEvent('publicAnnotationCountChanged', newCount); + emitEvent(sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, newCount); assert.equal(countEl1.textContent, newCount); assert.equal(countEl2.textContent, newCount); diff --git a/src/annotator/test/annotation-sync-test.js b/src/annotator/test/annotation-sync-test.js index cec1a63af71..af287e305c5 100644 --- a/src/annotator/test/annotation-sync-test.js +++ b/src/annotator/test/annotation-sync-test.js @@ -1,6 +1,9 @@ -import { EventBus } from '../util/emitter'; - +import { + guestToSidebarEvents, + sidebarToGuestEvents, +} from '../../shared/bridge-events'; import { AnnotationSync } from '../annotation-sync'; +import { EventBus } from '../util/emitter'; describe('AnnotationSync', () => { let createAnnotationSync; @@ -44,12 +47,12 @@ describe('AnnotationSync', () => { emitter.subscribe('annotationDeleted', eventStub); createAnnotationSync(); - publish('deleteAnnotation', { msg: ann }, () => {}); + publish(sidebarToGuestEvents.DELETE_ANNOTATION, { msg: ann }, () => {}); assert.calledWith(eventStub, ann); }); - it("calls the 'deleteAnnotation' event's callback function", done => { + it('calls the "deleteAnnotation" event\'s callback function', done => { const ann = { id: 1, $tag: 'tag1' }; const callback = function (err, result) { assert.isNull(err); @@ -58,7 +61,7 @@ describe('AnnotationSync', () => { }; createAnnotationSync(); - publish('deleteAnnotation', { msg: ann }, callback); + publish(sidebarToGuestEvents.DELETE_ANNOTATION, { msg: ann }, callback); }); it('deletes any existing annotation from its cache before publishing event to the annotator', done => { @@ -70,7 +73,7 @@ describe('AnnotationSync', () => { done(); }); - publish('deleteAnnotation', { msg: ann }, () => {}); + publish(sidebarToGuestEvents.DELETE_ANNOTATION, { msg: ann }, () => {}); }); it('deletes any existing annotation from its cache', () => { @@ -78,7 +81,7 @@ describe('AnnotationSync', () => { const annSync = createAnnotationSync(); annSync.cache.tag1 = ann; - publish('deleteAnnotation', { msg: ann }, () => {}); + publish(sidebarToGuestEvents.DELETE_ANNOTATION, { msg: ann }, () => {}); assert.isUndefined(annSync.cache.tag1); }); @@ -100,7 +103,7 @@ describe('AnnotationSync', () => { emitter.subscribe('annotationsLoaded', loadedStub); createAnnotationSync(); - publish('loadAnnotations', bodies, () => {}); + publish(sidebarToGuestEvents.LOAD_ANNOTATIONS, bodies, () => {}); assert.calledWith(loadedStub, annotations); }); @@ -118,10 +121,14 @@ describe('AnnotationSync', () => { emitter.publish('beforeAnnotationCreated', ann); assert.called(fakeBridge.call); - assert.calledWith(fakeBridge.call, 'beforeCreateAnnotation', { - msg: ann, - tag: ann.$tag, - }); + assert.calledWith( + fakeBridge.call, + guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, + { + msg: ann, + tag: ann.$tag, + } + ); }); it('assigns a non-empty tag to the annotation', () => { @@ -162,7 +169,9 @@ describe('AnnotationSync', () => { annotationSync.sync([ann]); - assert.calledWith(fakeBridge.call, 'sync', [{ msg: ann, tag: ann.$tag }]); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.SYNC, [ + { msg: ann, tag: ann.$tag }, + ]); }); }); @@ -173,8 +182,8 @@ describe('AnnotationSync', () => { annotationSync.destroy(); const cb = sinon.stub(); - publish('loadAnnotations', [ann], cb); - publish('deleteAnnotation', ann, cb); + publish(sidebarToGuestEvents.LOAD_ANNOTATIONS, [ann], cb); + publish(sidebarToGuestEvents.DELETE_ANNOTATION, ann, cb); assert.calledTwice(cb); assert.calledWith(cb.firstCall, null); diff --git a/src/annotator/test/features-test.js b/src/annotator/test/features-test.js index 881cfef4af2..99133d669f6 100644 --- a/src/annotator/test/features-test.js +++ b/src/annotator/test/features-test.js @@ -1,4 +1,4 @@ -import events from '../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../shared/bridge-events'; import { features, $imports } from '../features'; describe('features - annotation layer', () => { @@ -22,7 +22,7 @@ describe('features - annotation layer', () => { features.init({ on: function (topic, handler) { - if (topic === events.FEATURE_FLAGS_UPDATED) { + if (topic === sidebarToHostEvents.FEATURE_FLAGS_UPDATED) { featureFlagsUpdateHandler = handler; } }, diff --git a/src/annotator/test/guest-test.js b/src/annotator/test/guest-test.js index fdfc336a302..4e8bd087e37 100644 --- a/src/annotator/test/guest-test.js +++ b/src/annotator/test/guest-test.js @@ -1,6 +1,9 @@ -import Guest from '../guest'; +import { + guestToSidebarEvents, + sidebarToGuestEvents, +} from '../../shared/bridge-events'; +import Guest, { $imports } from '../guest'; import { EventBus } from '../util/emitter'; -import { $imports } from '../guest'; class FakeAdder { constructor(container, options) { @@ -234,7 +237,7 @@ describe('Guest', () => { }); describe('events from sidebar', () => { - const emitGuestEvent = (event, ...args) => { + const emitSidebarEvent = (event, ...args) => { for (let [evt, fn] of fakeBridge.on.args) { if (event === evt) { fn(...args); @@ -252,7 +255,7 @@ describe('Guest', () => { { annotation: { $tag: 'tag2' }, highlights: [highlight1] }, ]; - emitGuestEvent('focusAnnotations', ['tag1']); + emitSidebarEvent(sidebarToGuestEvents.FOCUS_ANNOTATIONS, ['tag1']); assert.calledWith( highlighter.setHighlightsFocused, @@ -270,7 +273,7 @@ describe('Guest', () => { { annotation: { $tag: 'tag2' }, highlights: [highlight1] }, ]; - emitGuestEvent('focusAnnotations', ['tag1']); + emitSidebarEvent(sidebarToGuestEvents.FOCUS_ANNOTATIONS, ['tag1']); assert.calledWith( highlighter.setHighlightsFocused, @@ -282,8 +285,11 @@ describe('Guest', () => { it('updates focused tag set', () => { const guest = createGuest(); - emitGuestEvent('focusAnnotations', ['tag1']); - emitGuestEvent('focusAnnotations', ['tag2', 'tag3']); + emitSidebarEvent(sidebarToGuestEvents.FOCUS_ANNOTATIONS, ['tag1']); + emitSidebarEvent(sidebarToGuestEvents.FOCUS_ANNOTATIONS, [ + 'tag2', + 'tag3', + ]); assert.deepEqual([...guest.focusedAnnotationTags], ['tag2', 'tag3']); }); @@ -302,7 +308,7 @@ describe('Guest', () => { }, ]; - emitGuestEvent('scrollToAnnotation', 'tag1'); + emitSidebarEvent(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, 'tag1'); assert.called(fakeIntegration.scrollToAnchor); assert.calledWith(fakeIntegration.scrollToAnchor, guest.anchors[0]); @@ -326,7 +332,7 @@ describe('Guest', () => { resolve(); }); - emitGuestEvent('scrollToAnnotation', 'tag1'); + emitSidebarEvent(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, 'tag1'); }); }); @@ -345,7 +351,7 @@ describe('Guest', () => { event.preventDefault() ); - emitGuestEvent('scrollToAnnotation', 'tag1'); + emitSidebarEvent(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, 'tag1'); assert.notCalled(fakeIntegration.scrollToAnchor); }); @@ -354,7 +360,7 @@ describe('Guest', () => { const guest = createGuest(); guest.anchors = [{ annotation: { $tag: 'tag1' } }]; - emitGuestEvent('scrollToAnnotation', 'tag1'); + emitSidebarEvent(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, 'tag1'); assert.notCalled(fakeIntegration.scrollToAnchor); }); @@ -374,7 +380,7 @@ describe('Guest', () => { const eventEmitted = sandbox.stub(); guest.element.addEventListener('scrolltorange', eventEmitted); - emitGuestEvent('scrollToAnnotation', 'tag1'); + emitSidebarEvent(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, 'tag1'); assert.notCalled(eventEmitted); assert.notCalled(fakeIntegration.scrollToAnchor); @@ -408,8 +414,8 @@ describe('Guest', () => { fakeIntegration.getMetadata.resolves(metadata); - emitGuestEvent( - 'getDocumentInfo', + emitSidebarEvent( + sidebarToGuestEvents.GET_DOCUMENT_INFO, createCallback('https://example.com/test.pdf', metadata, done) ); }); @@ -419,14 +425,14 @@ describe('Guest', () => { it('sets visibility of highlights in document', () => { const guest = createGuest(); - emitGuestEvent('setVisibleHighlights', true); + emitSidebarEvent(sidebarToGuestEvents.SET_VISIBLE_HIGHLIGHTS, true); assert.calledWith( highlighter.setHighlightsVisible, guest.element, true ); - emitGuestEvent('setVisibleHighlights', false); + emitSidebarEvent(sidebarToGuestEvents.SET_VISIBLE_HIGHLIGHTS, false); assert.calledWith( highlighter.setHighlightsVisible, guest.element, @@ -467,7 +473,7 @@ describe('Guest', () => { it('hides sidebar on user "mousedown" or "touchstart" events in the document', () => { for (let event of ['mousedown', 'touchstart']) { rootElement.dispatchEvent(new Event(event)); - assert.calledWith(fakeBridge.call, 'closeSidebar'); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.CLOSE_SIDEBAR); fakeBridge.call.resetHistory(); } }); @@ -511,13 +517,19 @@ describe('Guest', () => { // Hover the highlight fakeHighlight.dispatchEvent(new Event('mouseover', { bubbles: true })); assert.calledWith(highlighter.getHighlightsContainingNode, fakeHighlight); - assert.calledWith(fakeBridge.call, 'focusAnnotations', [ - 'highlight-ann-tag', - ]); + assert.calledWith( + fakeBridge.call, + sidebarToGuestEvents.FOCUS_ANNOTATIONS, + ['highlight-ann-tag'] + ); // Un-hover the highlight fakeHighlight.dispatchEvent(new Event('mouseout', { bubbles: true })); - assert.calledWith(fakeBridge.call, 'focusAnnotations', []); + assert.calledWith( + fakeBridge.call, + sidebarToGuestEvents.FOCUS_ANNOTATIONS, + [] + ); }); it('does not focus annotations in the sidebar when a non-highlight element is hovered', () => { @@ -540,10 +552,12 @@ describe('Guest', () => { it('selects annotations in the sidebar when clicking on a highlight', () => { fakeHighlight.dispatchEvent(new Event('mouseup', { bubbles: true })); - assert.calledWith(fakeBridge.call, 'showAnnotations', [ - 'highlight-ann-tag', - ]); - assert.calledWith(fakeBridge.call, 'openSidebar'); + assert.calledWith( + fakeBridge.call, + guestToSidebarEvents.SHOW_ANNOTATIONS, + ['highlight-ann-tag'] + ); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.OPEN_SIDEBAR); }); it('toggles selected annotations in the sidebar when Ctrl/Cmd-clicking a highlight', () => { @@ -551,10 +565,12 @@ describe('Guest', () => { new MouseEvent('mouseup', { bubbles: true, ctrlKey: true }) ); - assert.calledWith(fakeBridge.call, 'toggleAnnotationSelection', [ - 'highlight-ann-tag', - ]); - assert.calledWith(fakeBridge.call, 'openSidebar'); + assert.calledWith( + fakeBridge.call, + guestToSidebarEvents.TOGGLE_ANNOTATION_SELECTION, + ['highlight-ann-tag'] + ); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.OPEN_SIDEBAR); }); }); @@ -680,8 +696,12 @@ describe('Guest', () => { FakeAdder.instance.options.onShowAnnotations([{ $tag: 'ann1' }]); - assert.calledWith(fakeBridge.call, 'openSidebar'); - assert.calledWith(fakeBridge.call, 'showAnnotations', ['ann1']); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.OPEN_SIDEBAR); + assert.calledWith( + fakeBridge.call, + guestToSidebarEvents.SHOW_ANNOTATIONS, + ['ann1'] + ); }); }); @@ -692,7 +712,11 @@ describe('Guest', () => { guest.selectAnnotations(annotations); - assert.calledWith(fakeBridge.call, 'showAnnotations', ['ann1', 'ann2']); + assert.calledWith( + fakeBridge.call, + guestToSidebarEvents.SHOW_ANNOTATIONS, + ['ann1', 'ann2'] + ); }); it('toggles the annotations if `toggle` is true', () => { @@ -701,10 +725,11 @@ describe('Guest', () => { guest.selectAnnotations(annotations, true /* toggle */); - assert.calledWith(fakeBridge.call, 'toggleAnnotationSelection', [ - 'ann1', - 'ann2', - ]); + assert.calledWith( + fakeBridge.call, + guestToSidebarEvents.TOGGLE_ANNOTATION_SELECTION, + ['ann1', 'ann2'] + ); }); it('opens the sidebar', () => { @@ -712,7 +737,7 @@ describe('Guest', () => { guest.selectAnnotations([]); - assert.calledWith(fakeBridge.call, 'openSidebar'); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.OPEN_SIDEBAR); }); }); @@ -816,7 +841,7 @@ describe('Guest', () => { it('opens sidebar if `highlight` is false', async () => { const guest = createGuest(); await guest.createAnnotation(); - assert.calledWith(fakeBridge.call, 'openSidebar'); + assert.calledWith(fakeBridge.call, guestToSidebarEvents.OPEN_SIDEBAR); }); it('does not open sidebar if `highlight` is true', async () => { @@ -1050,7 +1075,7 @@ describe('Guest', () => { // Focus the annotation (in the sidebar) before it is anchored in the page. const [, focusAnnotationsCallback] = fakeBridge.on.args.find( - args => args[0] === 'focusAnnotations' + args => args[0] === guestToSidebarEvents.FOCUS_ANNOTATIONS ); focusAnnotationsCallback([annotation.$tag]); const anchors = await guest.anchor(annotation); diff --git a/src/annotator/test/sidebar-test.js b/src/annotator/test/sidebar-test.js index 47fbec3563b..a99b3516985 100644 --- a/src/annotator/test/sidebar-test.js +++ b/src/annotator/test/sidebar-test.js @@ -1,7 +1,8 @@ -import events from '../../shared/bridge-events'; - -import Sidebar, { MIN_RESIZE } from '../sidebar'; -import { $imports } from '../sidebar'; +import { + hostToSidebarEvents, + sidebarToHostEvents, +} from '../../shared/bridge-events'; +import Sidebar, { MIN_RESIZE, $imports } from '../sidebar'; import { EventBus } from '../util/emitter'; const DEFAULT_WIDTH = 350; @@ -204,7 +205,11 @@ describe('Sidebar', () => { }); window.dispatchEvent(event); - assert.calledWith(fakeBridge.call, 'destroyFrame', 'frame-id'); + assert.calledWith( + fakeBridge.call, + hostToSidebarEvents.DESTROY_FRAME, + 'frame-id' + ); }); function getConfigString(sidebar) { @@ -327,7 +332,7 @@ describe('Sidebar', () => { it('opens the frame', () => { const target = sandbox.stub(Sidebar.prototype, 'open'); createSidebar(); - emitEvent('openSidebar'); + emitEvent(sidebarToHostEvents.OPEN_SIDEBAR); assert.called(target); })); @@ -335,7 +340,7 @@ describe('Sidebar', () => { it('closes the frame', () => { const target = sandbox.stub(Sidebar.prototype, 'close'); createSidebar(); - emitEvent('closeSidebar'); + emitEvent(sidebarToHostEvents.CLOSE_SIDEBAR); assert.called(target); })); @@ -361,12 +366,12 @@ describe('Sidebar', () => { }); }); - describe('on LOGIN_REQUESTED event', () => { + describe('on "loginRequest" event', () => { it('calls the onLoginRequest callback function if one was provided', () => { const onLoginRequest = sandbox.stub(); createSidebar({ services: [{ onLoginRequest }] }); - emitEvent(events.LOGIN_REQUESTED); + emitEvent(sidebarToHostEvents.LOGIN_REQUESTED); assert.called(onLoginRequest); }); @@ -386,7 +391,7 @@ describe('Sidebar', () => { ], }); - emitEvent(events.LOGIN_REQUESTED); + emitEvent(sidebarToHostEvents.LOGIN_REQUESTED); assert.called(firstOnLogin); assert.notCalled(secondOnLogin); @@ -406,7 +411,7 @@ describe('Sidebar', () => { ], }); - emitEvent(events.LOGIN_REQUESTED); + emitEvent(sidebarToHostEvents.LOGIN_REQUESTED); assert.notCalled(secondOnLogin); assert.notCalled(thirdOnLogin); @@ -414,17 +419,17 @@ describe('Sidebar', () => { it('does not crash if there is no services', () => { createSidebar(); // No config.services - emitEvent(events.LOGIN_REQUESTED); + emitEvent(sidebarToHostEvents.LOGIN_REQUESTED); }); it('does not crash if services is an empty array', () => { createSidebar({ services: [] }); - emitEvent(events.LOGIN_REQUESTED); + emitEvent(sidebarToHostEvents.LOGIN_REQUESTED); }); it('does not crash if the first service has no onLoginRequest', () => { createSidebar({ services: [{}] }); - emitEvent(events.LOGIN_REQUESTED); + emitEvent(sidebarToHostEvents.LOGIN_REQUESTED); }); }); @@ -433,37 +438,37 @@ describe('Sidebar', () => { const onLogoutRequest = sandbox.stub(); createSidebar({ services: [{ onLogoutRequest }] }); - emitEvent(events.LOGOUT_REQUESTED); + emitEvent(sidebarToHostEvents.LOGOUT_REQUESTED); assert.called(onLogoutRequest); })); - describe('on SIGNUP_REQUESTED event', () => + describe('on "signupRequest" event', () => it('calls the onSignupRequest callback function', () => { const onSignupRequest = sandbox.stub(); createSidebar({ services: [{ onSignupRequest }] }); - emitEvent(events.SIGNUP_REQUESTED); + emitEvent(sidebarToHostEvents.SIGNUP_REQUESTED); assert.called(onSignupRequest); })); - describe('on PROFILE_REQUESTED event', () => + describe('on "profileRequest" event', () => it('calls the onProfileRequest callback function', () => { const onProfileRequest = sandbox.stub(); createSidebar({ services: [{ onProfileRequest }] }); - emitEvent(events.PROFILE_REQUESTED); + emitEvent(sidebarToHostEvents.PROFILE_REQUESTED); assert.called(onProfileRequest); })); - describe('on HELP_REQUESTED event', () => + describe('on "helpRequested" event', () => it('calls the onHelpRequest callback function', () => { const onHelpRequest = sandbox.stub(); createSidebar({ services: [{ onHelpRequest }] }); - emitEvent(events.HELP_REQUESTED); + emitEvent(sidebarToHostEvents.HELP_REQUESTED); assert.called(onHelpRequest); })); @@ -639,7 +644,11 @@ describe('Sidebar', () => { it('requests sidebar to set highlight visibility in guest frames', () => { const sidebar = createSidebar(); sidebar.setAllVisibleHighlights(true); - assert.calledWith(fakeBridge.call, 'setVisibleHighlights', true); + assert.calledWith( + fakeBridge.call, + hostToSidebarEvents.SET_VISIBLE_HIGHLIGHTS, + true + ); })); it('hides toolbar controls when using the "clean" theme', () => { diff --git a/src/shared/bridge-events.js b/src/shared/bridge-events.js index fad0aa4b12e..bd940cfcfaa 100644 --- a/src/shared/bridge-events.js +++ b/src/shared/bridge-events.js @@ -1,10 +1,123 @@ /** - * This module defines the set of global events that are dispatched - * across the bridge between the sidebar and annotator + * This module defines the set of global events that are dispatched across the + * the bridge/s between the sidebar-host and sidebar-guest/s. */ -export default { - // Events that the sidebar sends to the annotator - // ---------------------------------------------- + +/** + * Events that the host sends to the sidebar + * + * @typedef {'destroyFrame'|'setVisibleHighlights'|'sidebarOpened'} HostToSidebarEvent + * @type {Record<'DESTROY_FRAME'|'SET_VISIBLE_HIGHLIGHTS'|'SIDEBAR_OPENED', HostToSidebarEvent>} + */ +export const hostToSidebarEvents = { + /** + * The host is asking the sidebar to delete a frame. + */ + DESTROY_FRAME: 'destroyFrame', + + /** + * The host is asking the sidebar to set the annotation highlights on/off. + */ + SET_VISIBLE_HIGHLIGHTS: 'setVisibleHighlights', + + /** + * The host informs the sidebar that the sidebar has been opened. + */ + SIDEBAR_OPENED: 'sidebarOpened', +}; + +/** + * Events that the guest sends to the sidebar + * + * @typedef {'beforeCreateAnnotation'|'closeSidebar'|'focusAnnotations'|'openSidebar'|'showAnnotations'|'sync'|'toggleAnnotationSelection'} GuestToSidebarEvent + * @type {Record<'BEFORE_CREATE_ANNOTATION'|'CLOSE_SIDEBAR'|'FOCUS_ANNOTATIONS'|'OPEN_SIDEBAR'|'SHOW_ANNOTATIONS'|'SYNC'|'TOGGLE_ANNOTATION_SELECTION', GuestToSidebarEvent>} + */ +export const guestToSidebarEvents = { + /** + * The guest is asking the sidebar to create an annotation. + */ + BEFORE_CREATE_ANNOTATION: 'beforeCreateAnnotation', + + /** + * The guest is asking the sidebar to relay the message to open the sidebar. + */ + CLOSE_SIDEBAR: 'closeSidebar', + + /** + * The guest is asking the sidebar to focus on certain annotations. + */ + FOCUS_ANNOTATIONS: 'focusAnnotations', + + /** + * The guest is asking the sidebar to relay the message to open the sidebar. + */ + OPEN_SIDEBAR: 'openSidebar', + + /** + * The guest is asking the sidebar to display some annotations. + */ + SHOW_ANNOTATIONS: 'showAnnotations', + + /** + * The guest notifies the sidebar to synchronize about the anchoring status of annotations. + */ + SYNC: 'sync', + + /** + * The guest is asking the sidebar to toggle some annotations. + */ + TOGGLE_ANNOTATION_SELECTION: 'toggleAnnotationSelection', +}; + +/** + * Events that the sidebar sends to the guest/s + * + * @typedef {'deleteAnnotation'|'focusAnnotations'|'getDocumentInfo'|'loadAnnotations'|'scrollToAnnotation'|'setVisibleHighlights'} SidebarToGuestEvent + * @type {Record<'DELETE_ANNOTATION'|'FOCUS_ANNOTATIONS'|'GET_DOCUMENT_INFO'|'LOAD_ANNOTATIONS'|'SCROLL_TO_ANNOTATION'|'SET_VISIBLE_HIGHLIGHTS', SidebarToGuestEvent>} + */ +export const sidebarToGuestEvents = { + /** + * The sidebar is asking the guest/s to delete an annotation. + */ + DELETE_ANNOTATION: 'deleteAnnotation', + + /** + * The sidebar is asking the guest/s to focus on certain annotations. + */ + FOCUS_ANNOTATIONS: 'focusAnnotations', + + /** + * The sidebar is asking the guest/s to get the document metadata. + */ + GET_DOCUMENT_INFO: 'getDocumentInfo', + + /** + * The sidebar is asking the guest/s to load annotations. + */ + LOAD_ANNOTATIONS: 'loadAnnotations', + + /** + * The sidebar is asking the guest/s to scroll to certain annotation. + */ + SCROLL_TO_ANNOTATION: 'scrollToAnnotation', + + /** + * The sidebar relays to the guest/s to set the annotation highlights on/off. + */ + SET_VISIBLE_HIGHLIGHTS: 'setVisibleHighlights', +}; + +/** + * Events that the sidebar sends to the host + * + * @typedef {'closeSidebar'|'featureFlagsUpdated'|'helpRequested'|'loginRequested'|'logoutRequested'|'openNotebook'|'openSidebar'|'profileRequested'|'publicAnnotationCountChanged'|'signupRequested'} SidebarToHostEvent + * @type {Record<'CLOSE_SIDEBAR'|'FEATURE_FLAGS_UPDATED'|'HELP_REQUESTED'|'LOGIN_REQUESTED'|'LOGOUT_REQUESTED'|'OPEN_NOTEBOOK'|'OPEN_SIDEBAR'|'PROFILE_REQUESTED'|'PUBLIC_ANNOTATION_COUNT_CHANGED'|'SIGNUP_REQUESTED', SidebarToHostEvent>} + */ +export const sidebarToHostEvents = { + /** + * The sidebar relays to the host to open the sidebar. + */ + CLOSE_SIDEBAR: 'closeSidebar', /** * The updated feature flags for the user @@ -12,37 +125,51 @@ export default { FEATURE_FLAGS_UPDATED: 'featureFlagsUpdated', /** - * The sidebar is asking the annotator to open the partner site help page. + * The sidebar is asking the host to open the partner site help page. */ HELP_REQUESTED: 'helpRequested', - /** The sidebar is asking the annotator to do a partner site log in - * (for example, pop up a log in window). This is used when the client is - * embedded in a partner site and a log in button in the client is clicked. + /** + * The sidebar is asking the host to do a partner site log in + * (for example, pop up a log in window). This is used when the client is + * embedded in a partner site and a log in button in the client is clicked. */ LOGIN_REQUESTED: 'loginRequested', - /** The sidebar is asking the annotator to do a partner site log out. - * This is used when the client is embedded in a partner site and a log out - * button in the client is clicked. + /** + * The sidebar is asking the host to do a partner site log out. + * This is used when the client is embedded in a partner site and a log out + * button in the client is clicked. */ LOGOUT_REQUESTED: 'logoutRequested', /** - * The sidebar is asking the annotator to open the partner site profile page. + * The sidebar is asking the host to open the notebook. + */ + OPEN_NOTEBOOK: 'openNotebook', + + /** + * The sidebar is asking the host to open the sidebar (side-effect of + * creating an annotation). + */ + OPEN_SIDEBAR: 'openSidebar', + + /** + * The sidebar is asking the host to open the partner site profile page. */ PROFILE_REQUESTED: 'profileRequested', /** - * The set of annotations was updated. + * The sidebar inform the host to update the number of annotations in the partner site. */ PUBLIC_ANNOTATION_COUNT_CHANGED: 'publicAnnotationCountChanged', /** - * The sidebar is asking the annotator to do a partner site sign-up. + * The sidebar is asking the host to do a partner site sign-up. */ SIGNUP_REQUESTED: 'signupRequested', - - // Events that the annotator sends to the sidebar - // ---------------------------------------------- }; + +/** + * @typedef {HostToSidebarEvent|GuestToSidebarEvent|SidebarToGuestEvent|SidebarToHostEvent} BridgeEvent + */ diff --git a/src/shared/bridge.js b/src/shared/bridge.js index c777de42836..42f22ddc203 100644 --- a/src/shared/bridge.js +++ b/src/shared/bridge.js @@ -1,6 +1,9 @@ import { PortRPC } from './port-rpc'; -/** @typedef {import('../types/annotator').Destroyable} Destroyable */ +/** + * @typedef {import('./bridge-events').BridgeEvent} BridgeEvent + * @typedef {import('../types/annotator').Destroyable} Destroyable + */ /** * The Bridge service sets up a channel between frames and provides an events @@ -66,7 +69,7 @@ export class Bridge { * Make a method call on all channels, collect the results and pass them to a * callback when all results are collected. * - * @param {string} method - Name of remote method to call. + * @param {BridgeEvent} method - Name of remote method to call. * @param {any[]} args - Arguments to method. Final argument is an optional * callback with this type: `(error: string|Error|null, ...result: any[]) => void`. * This callback, if any, will be triggered once a response (via `postMessage`) @@ -132,7 +135,7 @@ export class Bridge { * Register a listener to be invoked when any connected channel sends a * message to this `Bridge`. * - * @param {string} method + * @param {'connect'|BridgeEvent} method * @param {(...args: any[]) => void} listener -- Final argument is an optional * callback of the type: `(error: string|Error|null, ...result: any[]) => void`. * This callback must be invoked in order to respond (via `postMessage`) diff --git a/src/sidebar/components/HypothesisApp.js b/src/sidebar/components/HypothesisApp.js index 03e89aea85e..2c09f457cd4 100644 --- a/src/sidebar/components/HypothesisApp.js +++ b/src/sidebar/components/HypothesisApp.js @@ -1,14 +1,14 @@ import classnames from 'classnames'; import { useEffect, useMemo } from 'preact/hooks'; -import bridgeEvents from '../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../shared/bridge-events'; import { confirm } from '../../shared/prompts'; import { serviceConfig } from '../config/service-config'; -import { useStoreProxy } from '../store/use-store'; import { parseAccountID } from '../helpers/account-id'; import { shouldAutoDisplayTutorial } from '../helpers/session'; import { applyTheme } from '../helpers/theme'; import { withServices } from '../service-context'; +import { useStoreProxy } from '../store/use-store'; import AnnotationView from './AnnotationView'; import SidebarView from './SidebarView'; @@ -98,7 +98,7 @@ function HypothesisApp({ auth, frameSync, settings, session, toastMessenger }) { const login = async () => { if (serviceConfig(settings)) { // Let the host page handle the login request - frameSync.notifyHost(bridgeEvents.LOGIN_REQUESTED); + frameSync.notifyHost(sidebarToHostEvents.LOGIN_REQUESTED); return; } @@ -116,7 +116,7 @@ function HypothesisApp({ auth, frameSync, settings, session, toastMessenger }) { const signUp = () => { if (serviceConfig(settings)) { // Let the host page handle the signup request - frameSync.notifyHost(bridgeEvents.SIGNUP_REQUESTED); + frameSync.notifyHost(sidebarToHostEvents.SIGNUP_REQUESTED); return; } window.open(store.getLink('signup')); @@ -156,7 +156,7 @@ function HypothesisApp({ auth, frameSync, settings, session, toastMessenger }) { store.discardAllDrafts(); if (serviceConfig(settings)) { - frameSync.notifyHost(bridgeEvents.LOGOUT_REQUESTED); + frameSync.notifyHost(sidebarToHostEvents.LOGOUT_REQUESTED); return; } diff --git a/src/sidebar/components/TopBar.js b/src/sidebar/components/TopBar.js index fb7ad296010..392c7eb594a 100644 --- a/src/sidebar/components/TopBar.js +++ b/src/sidebar/components/TopBar.js @@ -1,11 +1,11 @@ import { IconButton, LinkButton } from '@hypothesis/frontend-shared'; -import bridgeEvents from '../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../shared/bridge-events'; import { serviceConfig } from '../config/service-config'; -import { useStoreProxy } from '../store/use-store'; import { isThirdPartyService } from '../helpers/is-third-party-service'; -import { withServices } from '../service-context'; import { applyTheme } from '../helpers/theme'; +import { withServices } from '../service-context'; +import { useStoreProxy } from '../store/use-store'; import GroupList from './GroupList'; import SearchInput from './SearchInput'; @@ -71,7 +71,7 @@ function TopBar({ const requestHelp = () => { const service = serviceConfig(settings); if (service && service.onHelpRequestProvided) { - frameSync.notifyHost(bridgeEvents.HELP_REQUESTED); + frameSync.notifyHost(sidebarToHostEvents.HELP_REQUESTED); } else { store.toggleSidebarPanel('help'); } diff --git a/src/sidebar/components/UserMenu.js b/src/sidebar/components/UserMenu.js index 77b574f5766..674bc78f1d6 100644 --- a/src/sidebar/components/UserMenu.js +++ b/src/sidebar/components/UserMenu.js @@ -1,11 +1,11 @@ import { SvgIcon } from '@hypothesis/frontend-shared'; import { useState } from 'preact/hooks'; -import bridgeEvents from '../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../shared/bridge-events'; import { serviceConfig } from '../config/service-config'; import { isThirdPartyUser } from '../helpers/account-id'; -import { useStoreProxy } from '../store/use-store'; import { withServices } from '../service-context'; +import { useStoreProxy } from '../store/use-store'; import Menu from './Menu'; import MenuItem from './MenuItem'; @@ -57,7 +57,10 @@ function UserMenu({ auth, frameSync, onLogout, settings }) { !isThirdParty || serviceSupports('onLogoutRequestProvided'); const onSelectNotebook = () => { - frameSync.notifyHost('openNotebook', store.focusedGroupId()); + frameSync.notifyHost( + sidebarToHostEvents.OPEN_NOTEBOOK, + store.focusedGroupId() + ); }; // Temporary access to the Notebook without feature flag: @@ -70,7 +73,7 @@ function UserMenu({ auth, frameSync, onLogout, settings }) { }; const onProfileSelected = () => - isThirdParty && frameSync.notifyHost(bridgeEvents.PROFILE_REQUESTED); + isThirdParty && frameSync.notifyHost(sidebarToHostEvents.PROFILE_REQUESTED); // Generate dynamic props for the profile component const profileItemProps = (() => { diff --git a/src/sidebar/components/test/HypothesisApp-test.js b/src/sidebar/components/test/HypothesisApp-test.js index 617106b2e8d..367daabb246 100644 --- a/src/sidebar/components/test/HypothesisApp-test.js +++ b/src/sidebar/components/test/HypothesisApp-test.js @@ -1,8 +1,7 @@ import { mount } from 'enzyme'; -import bridgeEvents from '../../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../../shared/bridge-events'; import mockImportedComponents from '../../../test-util/mock-imported-components'; - import HypothesisApp, { $imports } from '../HypothesisApp'; describe('HypothesisApp', () => { @@ -225,12 +224,12 @@ describe('HypothesisApp', () => { fakeServiceConfig.returns({}); }); - it('sends SIGNUP_REQUESTED event', () => { + it('sends "signupRequest" event', () => { const wrapper = createComponent(); clickSignUp(wrapper); assert.calledWith( fakeFrameSync.notifyHost, - bridgeEvents.SIGNUP_REQUESTED + sidebarToHostEvents.SIGNUP_REQUESTED ); }); @@ -304,7 +303,9 @@ describe('HypothesisApp', () => { assert.equal(fakeFrameSync.notifyHost.callCount, 1); assert.isTrue( - fakeFrameSync.notifyHost.calledWithExactly(bridgeEvents.LOGIN_REQUESTED) + fakeFrameSync.notifyHost.calledWithExactly( + sidebarToHostEvents.LOGIN_REQUESTED + ) ); }); }); @@ -414,7 +415,7 @@ describe('HypothesisApp', () => { assert.calledOnce(fakeFrameSync.notifyHost); assert.calledWithExactly( fakeFrameSync.notifyHost, - bridgeEvents.LOGOUT_REQUESTED + sidebarToHostEvents.LOGOUT_REQUESTED ); }); diff --git a/src/sidebar/components/test/TopBar-test.js b/src/sidebar/components/test/TopBar-test.js index 7074d3afba2..afcc5aa1e65 100644 --- a/src/sidebar/components/test/TopBar-test.js +++ b/src/sidebar/components/test/TopBar-test.js @@ -1,9 +1,7 @@ import { mount } from 'enzyme'; -import bridgeEvents from '../../../shared/bridge-events'; -import TopBar from '../TopBar'; -import { $imports } from '../TopBar'; - +import { sidebarToHostEvents } from '../../../shared/bridge-events'; +import TopBar, { $imports } from '../TopBar'; import { checkAccessibility } from '../../../test-util/accessibility'; import mockImportedComponents from '../../../test-util/mock-imported-components'; @@ -125,7 +123,7 @@ describe('TopBar', () => { assert.equal(fakeStore.toggleSidebarPanel.callCount, 0); assert.calledWith( fakeFrameSync.notifyHost, - bridgeEvents.HELP_REQUESTED + sidebarToHostEvents.HELP_REQUESTED ); }); }); diff --git a/src/sidebar/components/test/UserMenu-test.js b/src/sidebar/components/test/UserMenu-test.js index a31ff56e5e5..6e917e1e788 100644 --- a/src/sidebar/components/test/UserMenu-test.js +++ b/src/sidebar/components/test/UserMenu-test.js @@ -1,11 +1,9 @@ import { mount } from 'enzyme'; import { act } from 'preact/test-utils'; -import bridgeEvents from '../../../shared/bridge-events'; -import UserMenu from '../UserMenu'; -import { $imports } from '../UserMenu'; - +import { sidebarToHostEvents } from '../../../shared/bridge-events'; import mockImportedComponents from '../../../test-util/mock-imported-components'; +import UserMenu, { $imports } from '../UserMenu'; describe('UserMenu', () => { let fakeAuth; @@ -150,7 +148,7 @@ describe('UserMenu', () => { assert.equal(fakeFrameSync.notifyHost.callCount, 1); assert.calledWith( fakeFrameSync.notifyHost, - bridgeEvents.PROFILE_REQUESTED + sidebarToHostEvents.PROFILE_REQUESTED ); }); diff --git a/src/sidebar/services/features.js b/src/sidebar/services/features.js index d8856c82868..c847e2d17e6 100644 --- a/src/sidebar/services/features.js +++ b/src/sidebar/services/features.js @@ -1,4 +1,4 @@ -import bridgeEvents from '../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../shared/bridge-events'; import { watch } from '../util/watch'; /** @@ -27,7 +27,7 @@ export class FeaturesService { const currentFlags = () => this._store.profile().features; const sendFeatureFlags = () => { this._frameSync.notifyHost( - bridgeEvents.FEATURE_FLAGS_UPDATED, + sidebarToHostEvents.FEATURE_FLAGS_UPDATED, currentFlags() || {} ); }; diff --git a/src/sidebar/services/frame-sync.js b/src/sidebar/services/frame-sync.js index a240c3e9abd..c7a0b4ed1cb 100644 --- a/src/sidebar/services/frame-sync.js +++ b/src/sidebar/services/frame-sync.js @@ -1,7 +1,12 @@ import debounce from 'lodash.debounce'; -import bridgeEvents from '../../shared/bridge-events'; import { Bridge } from '../../shared/bridge'; +import { + hostToSidebarEvents, + guestToSidebarEvents, + sidebarToHostEvents, + sidebarToGuestEvents, +} from '../../shared/bridge-events'; import { isReply, isPublic } from '../helpers/annotation-metadata'; import { watch } from '../util/watch'; @@ -105,13 +110,19 @@ export class FrameSyncService { // when they are added or removed in the sidebar, but not re-anchoring // annotations if their selectors are updated. if (added.length > 0) { - this._guestRPC.call('loadAnnotations', added.map(formatAnnot)); + this._guestRPC.call( + sidebarToGuestEvents.LOAD_ANNOTATIONS, + added.map(formatAnnot) + ); added.forEach(annot => { inFrame.add(annot.$tag); }); } deleted.forEach(annot => { - this._guestRPC.call('deleteAnnotation', formatAnnot(annot)); + this._guestRPC.call( + sidebarToGuestEvents.DELETE_ANNOTATION, + formatAnnot(annot) + ); inFrame.delete(annot.$tag); }); @@ -119,7 +130,7 @@ export class FrameSyncService { if (frames.every(frame => frame.isAnnotationFetchComplete)) { if (publicAnns === 0 || publicAnns !== prevPublicAnns) { this._hostRPC.call( - bridgeEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, + sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, publicAnns ); prevPublicAnns = publicAnns; @@ -136,23 +147,29 @@ export class FrameSyncService { */ this._setupSyncFromGuests = () => { // A new annotation, note or highlight was created in the frame - this._guestRPC.on('beforeCreateAnnotation', event => { - const annot = Object.assign({}, event.msg, { $tag: event.tag }); - // If user is not logged in, we can't really create a meaningful highlight - // or annotation. Instead, we need to open the sidebar, show an error, - // and delete the (unsaved) annotation so it gets un-selected in the - // target document - if (!store.isLoggedIn()) { - this._hostRPC.call('openSidebar'); - store.openSidebarPanel('loginPrompt'); - this._guestRPC.call('deleteAnnotation', formatAnnot(annot)); - return; - } - inFrame.add(event.tag); + this._guestRPC.on( + guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, + event => { + const annot = Object.assign({}, event.msg, { $tag: event.tag }); + // If user is not logged in, we can't really create a meaningful highlight + // or annotation. Instead, we need to open the sidebar, show an error, + // and delete the (unsaved) annotation so it gets un-selected in the + // target document + if (!store.isLoggedIn()) { + this._hostRPC.call(sidebarToHostEvents.OPEN_SIDEBAR); + store.openSidebarPanel('loginPrompt'); + this._guestRPC.call( + sidebarToGuestEvents.DELETE_ANNOTATION, + formatAnnot(annot) + ); + return; + } + inFrame.add(event.tag); - // Create the new annotation in the sidebar. - annotationsService.create(annot); - }); + // Create the new annotation in the sidebar. + annotationsService.create(annot); + } + ); // Map of annotation tag to anchoring status // ('anchored'|'orphan'|'timeout'). @@ -168,7 +185,7 @@ export class FrameSyncService { }, 10); // Anchoring an annotation in the frame completed - this._guestRPC.on('sync', events_ => { + this._guestRPC.on(guestToSidebarEvents.SYNC, events_ => { events_.forEach(event => { inFrame.add(event.tag); anchoringStatusUpdates[event.tag] = event.msg.$orphan @@ -178,25 +195,28 @@ export class FrameSyncService { }); }); - this._guestRPC.on('showAnnotations', tags => { + this._guestRPC.on(guestToSidebarEvents.SHOW_ANNOTATIONS, tags => { store.selectAnnotations(store.findIDsForTags(tags)); store.selectTab('annotation'); }); - this._guestRPC.on('focusAnnotations', tags => { + this._guestRPC.on(guestToSidebarEvents.FOCUS_ANNOTATIONS, tags => { store.focusAnnotations(tags || []); }); - this._guestRPC.on('toggleAnnotationSelection', tags => { - store.toggleSelectedAnnotations(store.findIDsForTags(tags)); - }); + this._guestRPC.on( + guestToSidebarEvents.TOGGLE_ANNOTATION_SELECTION, + tags => { + store.toggleSelectedAnnotations(store.findIDsForTags(tags)); + } + ); - this._guestRPC.on('openSidebar', () => { - this._hostRPC.call('openSidebar'); + this._guestRPC.on(guestToSidebarEvents.OPEN_SIDEBAR, () => { + this._hostRPC.call(sidebarToHostEvents.OPEN_SIDEBAR); }); - this._guestRPC.on('closeSidebar', () => { - this._hostRPC.call('closeSidebar'); + this._guestRPC.on(guestToSidebarEvents.CLOSE_SIDEBAR, () => { + this._hostRPC.call(sidebarToHostEvents.CLOSE_SIDEBAR); }); }; } @@ -212,7 +232,7 @@ export class FrameSyncService { * @param {PortRPC} channel */ const addFrame = channel => { - channel.call('getDocumentInfo', (err, info) => { + channel.call(sidebarToGuestEvents.GET_DOCUMENT_INFO, (err, info) => { if (err) { channel.destroy(); return; @@ -249,14 +269,14 @@ export class FrameSyncService { this._setupSyncToGuests(); this._setupSyncFromGuests(); - this._hostRPC.on('sidebarOpened', () => { + this._hostRPC.on(hostToSidebarEvents.SIDEBAR_OPENED, () => { this._store.setSidebarOpened(true); }); // Listen for notifications of a guest being unloaded. This message is routed // via the host frame rather than coming directly from the unloaded guest // to work around https://bugs.webkit.org/show_bug.cgi?id=231167. - this._hostRPC.on('destroyFrame', frameIdentifier => { + this._hostRPC.on(hostToSidebarEvents.DESTROY_FRAME, frameIdentifier => { const frame = this._store.frames().find(f => f.id === frameIdentifier); if (frame) { this._store.destroyFrame(frame); @@ -265,8 +285,8 @@ export class FrameSyncService { // When user toggles the highlight visibility control in the sidebar container, // update the visibility in all the guest frames. - this._hostRPC.on('setVisibleHighlights', state => { - this._guestRPC.call('setVisibleHighlights', state); + this._hostRPC.on(hostToSidebarEvents.SET_VISIBLE_HIGHLIGHTS, state => { + this._guestRPC.call(sidebarToGuestEvents.SET_VISIBLE_HIGHLIGHTS, state); }); // Create channel for sidebar <-> host communication and send port to host. @@ -282,7 +302,7 @@ export class FrameSyncService { /** * Send an RPC message to the host frame. * - * @param {string} method + * @param {import('../../shared/bridge-events').SidebarToHostEvent} method * @param {any[]} args */ notifyHost(method, ...args) { @@ -299,7 +319,7 @@ export class FrameSyncService { */ focusAnnotations(tags) { this._store.focusAnnotations(tags); - this._guestRPC.call('focusAnnotations', tags); + this._guestRPC.call(sidebarToGuestEvents.FOCUS_ANNOTATIONS, tags); } /** @@ -308,6 +328,6 @@ export class FrameSyncService { * @param {string} tag */ scrollToAnnotation(tag) { - this._guestRPC.call('scrollToAnnotation', tag); + this._guestRPC.call(sidebarToGuestEvents.SCROLL_TO_ANNOTATION, tag); } } diff --git a/src/sidebar/services/test/features-test.js b/src/sidebar/services/test/features-test.js index 55be3a7f0b6..b33454d8379 100644 --- a/src/sidebar/services/test/features-test.js +++ b/src/sidebar/services/test/features-test.js @@ -1,4 +1,4 @@ -import bridgeEvents from '../../../shared/bridge-events'; +import { sidebarToHostEvents } from '../../../shared/bridge-events'; import { FeaturesService } from '../features'; describe('FeaturesService', () => { @@ -52,7 +52,7 @@ describe('FeaturesService', () => { assert.calledWith( fakeFrameSync.notifyHost, - bridgeEvents.FEATURE_FLAGS_UPDATED, + sidebarToHostEvents.FEATURE_FLAGS_UPDATED, fakeStore.profile().features ); }); @@ -71,7 +71,7 @@ describe('FeaturesService', () => { assert.calledWith( fakeFrameSync.notifyHost, - bridgeEvents.FEATURE_FLAGS_UPDATED, + sidebarToHostEvents.FEATURE_FLAGS_UPDATED, fakeStore.profile().features ); }); diff --git a/src/sidebar/services/test/frame-sync-test.js b/src/sidebar/services/test/frame-sync-test.js index 8e4815726c6..5241daa217d 100644 --- a/src/sidebar/services/test/frame-sync-test.js +++ b/src/sidebar/services/test/frame-sync-test.js @@ -1,5 +1,11 @@ import EventEmitter from 'tiny-emitter'; +import { + hostToSidebarEvents, + guestToSidebarEvents, + sidebarToHostEvents, + sidebarToGuestEvents, +} from '../../../shared/bridge-events'; import { Injector } from '../../../shared/injector'; import * as annotationFixtures from '../../test/annotation-fixtures'; import createFakeStore from '../../test/fake-redux-store'; @@ -206,7 +212,7 @@ describe('FrameSyncService', () => { assert.calledWithMatch( guestBridge().call, - 'loadAnnotations', + sidebarToGuestEvents.LOAD_ANNOTATIONS, sinon.match([formatAnnot(fixtures.ann)]) ); }); @@ -224,7 +230,7 @@ describe('FrameSyncService', () => { assert.calledWithMatch( guestBridge().call, - 'loadAnnotations', + sidebarToGuestEvents.LOAD_ANNOTATIONS, sinon.match([formatAnnot(ann2)]) ); }); @@ -234,7 +240,9 @@ describe('FrameSyncService', () => { annotations: [annotationFixtures.newReply()], }); - assert.isFalse(guestBridge().call.calledWith('loadAnnotations')); + assert.isFalse( + guestBridge().call.calledWith(sidebarToGuestEvents.LOAD_ANNOTATIONS) + ); }); }); @@ -249,7 +257,7 @@ describe('FrameSyncService', () => { }); assert.calledWithMatch( hostBridge().call, - 'publicAnnotationCountChanged', + sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, sinon.match(1) ); }); @@ -264,7 +272,7 @@ describe('FrameSyncService', () => { assert.calledWithMatch( hostBridge().call, - 'publicAnnotationCountChanged', + sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, sinon.match(0) ); }); @@ -275,7 +283,9 @@ describe('FrameSyncService', () => { annotations: [annotationFixtures.publicAnnotation()], }); assert.isFalse( - hostBridge().call.calledWith('publicAnnotationCountChanged') + hostBridge().call.calledWith( + sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED + ) ); }); @@ -285,7 +295,9 @@ describe('FrameSyncService', () => { annotations: [annotationFixtures.publicAnnotation()], }); assert.isFalse( - hostBridge().call.calledWith('publicAnnotationCountChanged') + hostBridge().call.calledWith( + sidebarToHostEvents.PUBLIC_ANNOTATION_COUNT_CHANGED + ) ); }); }); @@ -303,7 +315,7 @@ describe('FrameSyncService', () => { assert.calledWithMatch( guestBridge().call, - 'deleteAnnotation', + sidebarToGuestEvents.DELETE_ANNOTATION, sinon.match(formatAnnot(fixtures.ann)) ); }); @@ -316,7 +328,10 @@ describe('FrameSyncService', () => { fakeStore.isLoggedIn.returns(true); const ann = { target: [] }; - guestBridge().emit('beforeCreateAnnotation', { tag: 't1', msg: ann }); + guestBridge().emit(guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, { + tag: 't1', + msg: ann, + }); assert.calledWith( fakeAnnotationsService.create, @@ -337,30 +352,45 @@ describe('FrameSyncService', () => { it('should not create an annotation in the sidebar', () => { const ann = { target: [] }; - guestBridge().emit('beforeCreateAnnotation', { tag: 't1', msg: ann }); + guestBridge().emit(guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, { + tag: 't1', + msg: ann, + }); assert.notCalled(fakeAnnotationsService.create); }); it('should open the sidebar', () => { const ann = { target: [] }; - guestBridge().emit('beforeCreateAnnotation', { tag: 't1', msg: ann }); + guestBridge().emit(guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, { + tag: 't1', + msg: ann, + }); - assert.calledWith(hostBridge().call, 'openSidebar'); + assert.calledWith(hostBridge().call, sidebarToHostEvents.OPEN_SIDEBAR); }); it('should open the login prompt panel', () => { const ann = { target: [] }; - guestBridge().emit('beforeCreateAnnotation', { tag: 't1', msg: ann }); + guestBridge().emit(guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, { + tag: 't1', + msg: ann, + }); assert.calledWith(fakeStore.openSidebarPanel, 'loginPrompt'); }); it('should send a "deleteAnnotation" message to the frame', () => { const ann = { target: [] }; - guestBridge().emit('beforeCreateAnnotation', { tag: 't1', msg: ann }); + guestBridge().emit(guestToSidebarEvents.BEFORE_CREATE_ANNOTATION, { + tag: 't1', + msg: ann, + }); - assert.calledWith(guestBridge().call, 'deleteAnnotation'); + assert.calledWith( + guestBridge().call, + sidebarToGuestEvents.DELETE_ANNOTATION + ); }); }); }); @@ -384,7 +414,9 @@ describe('FrameSyncService', () => { } it('updates the anchoring status for the annotation', () => { - guestBridge().emit('sync', [{ tag: 't1', msg: { $orphan: false } }]); + guestBridge().emit(guestToSidebarEvents.SYNC, [ + { tag: 't1', msg: { $orphan: false } }, + ]); expireDebounceTimeout(); @@ -392,8 +424,12 @@ describe('FrameSyncService', () => { }); it('coalesces multiple "sync" messages', () => { - guestBridge().emit('sync', [{ tag: 't1', msg: { $orphan: false } }]); - guestBridge().emit('sync', [{ tag: 't2', msg: { $orphan: true } }]); + guestBridge().emit(guestToSidebarEvents.SYNC, [ + { tag: 't1', msg: { $orphan: false } }, + ]); + guestBridge().emit(guestToSidebarEvents.SYNC, [ + { tag: 't2', msg: { $orphan: true } }, + ]); expireDebounceTimeout(); @@ -444,7 +480,7 @@ describe('FrameSyncService', () => { it('removes the frame from the frames list', () => { frameSync.connect(); - hostBridge().emit('destroyFrame', frameId); + hostBridge().emit(hostToSidebarEvents.DESTROY_FRAME, frameId); assert.calledWith(fakeStore.destroyFrame, fixtures.framesListEntry); }); @@ -454,7 +490,11 @@ describe('FrameSyncService', () => { it('selects annotations which have an ID', () => { frameSync.connect(); fakeStore.findIDsForTags.returns(['id1', 'id2', 'id3']); - guestBridge().emit('showAnnotations', ['tag1', 'tag2', 'tag3']); + guestBridge().emit(guestToSidebarEvents.SHOW_ANNOTATIONS, [ + 'tag1', + 'tag2', + 'tag3', + ]); assert.calledWith(fakeStore.selectAnnotations, ['id1', 'id2', 'id3']); assert.calledWith(fakeStore.selectTab, 'annotation'); @@ -464,7 +504,11 @@ describe('FrameSyncService', () => { describe('on "focusAnnotations" message', () => { it('focuses the annotations', () => { frameSync.connect(); - guestBridge().emit('focusAnnotations', ['tag1', 'tag2', 'tag3']); + guestBridge().emit(guestToSidebarEvents.FOCUS_ANNOTATIONS, [ + 'tag1', + 'tag2', + 'tag3', + ]); assert.calledWith(fakeStore.focusAnnotations, ['tag1', 'tag2', 'tag3']); }); }); @@ -473,7 +517,11 @@ describe('FrameSyncService', () => { it('toggles the selected state of the annotations', () => { frameSync.connect(); fakeStore.findIDsForTags.returns(['id1', 'id2', 'id3']); - guestBridge().emit('toggleAnnotationSelection', ['tag1', 'tag2', 'tag3']); + guestBridge().emit(guestToSidebarEvents.TOGGLE_ANNOTATION_SELECTION, [ + 'tag1', + 'tag2', + 'tag3', + ]); assert.calledWith(fakeStore.toggleSelectedAnnotations, [ 'id1', 'id2', @@ -485,7 +533,7 @@ describe('FrameSyncService', () => { describe('on "sidebarOpened" message', () => { it('sets the sidebar open in the store', () => { frameSync.connect(); - hostBridge().emit('sidebarOpened'); + hostBridge().emit(hostToSidebarEvents.SIDEBAR_OPENED); assert.calledWith(fakeStore.setSidebarOpened, true); }); @@ -497,21 +545,24 @@ describe('FrameSyncService', () => { }); it('calls "openSidebar"', () => { - guestBridge().emit('openSidebar'); + guestBridge().emit(guestToSidebarEvents.OPEN_SIDEBAR); - assert.calledWith(hostBridge().call, 'openSidebar'); + assert.calledWith(hostBridge().call, sidebarToHostEvents.OPEN_SIDEBAR); }); it('calls "closeSidebar"', () => { - guestBridge().emit('closeSidebar'); + guestBridge().emit(guestToSidebarEvents.CLOSE_SIDEBAR); - assert.calledWith(hostBridge().call, 'closeSidebar'); + assert.calledWith(hostBridge().call, sidebarToHostEvents.CLOSE_SIDEBAR); }); it('calls "setVisibleHighlights"', () => { - hostBridge().emit('setVisibleHighlights'); + hostBridge().emit(hostToSidebarEvents.SET_VISIBLE_HIGHLIGHTS); - assert.calledWith(guestBridge().call, 'setVisibleHighlights'); + assert.calledWith( + guestBridge().call, + sidebarToGuestEvents.SET_VISIBLE_HIGHLIGHTS + ); }); }); @@ -532,7 +583,7 @@ describe('FrameSyncService', () => { frameSync.focusAnnotations([1, 2]); assert.calledWith( guestBridge().call, - 'focusAnnotations', + sidebarToGuestEvents.FOCUS_ANNOTATIONS, sinon.match.array.deepEquals([1, 2]) ); }); @@ -542,7 +593,11 @@ describe('FrameSyncService', () => { it('should scroll to the annotation in the guest', () => { frameSync.connect(); frameSync.scrollToAnnotation('atag'); - assert.calledWith(guestBridge().call, 'scrollToAnnotation', 'atag'); + assert.calledWith( + guestBridge().call, + sidebarToGuestEvents.SCROLL_TO_ANNOTATION, + 'atag' + ); }); });