Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/sidebar/services/annotation-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import * as postMessageJsonRpc from '../util/postmessage-json-rpc';
export class AnnotationActivityService {
private _rpc: SidebarSettings['rpc'];
private _reportConfig: SidebarSettings['reportActivity'];
private _reportDocumentInfo: boolean;

constructor(settings: SidebarSettings) {
this._rpc = settings.rpc;
this._reportConfig = settings.reportActivity;
this._reportDocumentInfo = settings.reportDocumentInfo ?? false;
}

reportActivity(eventType: AnnotationEventType, annotation: Annotation) {
Expand Down Expand Up @@ -51,6 +53,18 @@ export class AnnotationActivityService {
}
}

/**
* Notify the embedder frame of the loaded document's identity.
*
* Only sent when the embedder opts in via the `reportDocumentInfo` setting.
*/
reportDocumentInfo(uri: string) {
if (!this._reportDocumentInfo) {
return;
}
this.notify('reportDocumentInfo', [{ uri }]);
}

/**
* Notify the embedder frame that the client has, or does not have,
* unsaved changes to annotations.
Expand Down
14 changes: 14 additions & 0 deletions src/sidebar/services/frame-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { isPrivate } from '../helpers/permissions';
import type { SidebarStore } from '../store';
import type { Frame } from '../store/modules/frames';
import { watch } from '../util/watch';
import type { AnnotationActivityService } from './annotation-activity';
import type { AnnotationsService } from './annotations';
import type { ToastMessengerService } from './toast-messenger';

Expand Down Expand Up @@ -111,6 +112,7 @@ function frameForAnnotation(frames: Frame[], ann: Annotation): Frame | null {
* @inject
*/
export class FrameSyncService {
private _annotationActivity: AnnotationActivityService;
private _annotationsService: AnnotationsService;

/**
Expand Down Expand Up @@ -183,11 +185,13 @@ export class FrameSyncService {

constructor(
$window: Window,
annotationActivity: AnnotationActivityService,
annotationsService: AnnotationsService,
store: SidebarStore,
toastMessenger: ToastMessengerService,
) {
this._window = $window;
this._annotationActivity = annotationActivity;
this._annotationsService = annotationsService;
this._store = store;
this._toastMessenger = toastMessenger;
Expand Down Expand Up @@ -395,6 +399,16 @@ export class FrameSyncService {
segment: info.segmentInfo,
persistent: info.persistent,
});

if (sourceId === null) {
// For a PDF the main URI is the (possibly temporary) URL it was served
// from, so prefer the fingerprint URN that `PDFMetadata` puts in
// `link` — that is the stable identity annotations resolve onto.
const fingerprintURN = info.metadata?.link?.find(link =>
link.href.startsWith('urn:x-pdf:'),
)?.href;
this._annotationActivity.reportDocumentInfo(fingerprintURN ?? info.uri);
}
});

// TODO - Close connection if we don't receive a "connect" message within
Expand Down
28 changes: 28 additions & 0 deletions src/sidebar/services/test/annotation-activity-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,34 @@ describe('AnnotationActivityService', () => {
});
});

describe('#reportDocumentInfo', () => {
it('sends reportDocumentInfo notification if the embedder opted in', () => {
const svc = new AnnotationActivityService({
...fakeSettings,
reportDocumentInfo: true,
});

svc.reportDocumentInfo('urn:x-pdf:FINGERPRINT');

assert.calledOnce(fakePostMessageJsonRpc.notify);
assert.calledWith(
fakePostMessageJsonRpc.notify,
window,
'https://www.example.com',
'reportDocumentInfo',
[{ uri: 'urn:x-pdf:FINGERPRINT' }],
);
});

it('does not send notification if the embedder did not opt in', () => {
const svc = new AnnotationActivityService(fakeSettings);

svc.reportDocumentInfo('urn:x-pdf:FINGERPRINT');

assert.notCalled(fakePostMessageJsonRpc.notify);
});
});

describe('#notifyUnsavedChanges', () => {
[true, false].forEach(unsaved => {
it('sends reportUnsavedChanges notification with unsaved state', () => {
Expand Down
45 changes: 45 additions & 0 deletions src/sidebar/services/test/frame-sync-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ const fixtures = {
persistent: false,
},

// Argument to the `documentInfoChanged` call made by a guest displaying a PDF.
pdfDocumentInfo: {
uri: 'https://example.com/test.pdf',
metadata: {
documentFingerprint: 'FINGERPRINT',
link: [
{ href: 'urn:x-pdf:FINGERPRINT' },
{ href: 'https://example.com/test.pdf' },
],
},
persistent: false,
},

// Argument to the `documentInfoChanged` call made by a guest displaying an EPUB
// document.
epubDocumentInfo: {
Expand All @@ -55,6 +68,7 @@ const fixtures = {
describe('FrameSyncService', () => {
let FakePortRPC;

let fakeAnnotationActivity;
let fakeAnnotationsService;
let fakeToastMessenger;
let fakePortRPCs;
Expand All @@ -76,6 +90,7 @@ describe('FrameSyncService', () => {
let setupPortRPC;

beforeEach(() => {
fakeAnnotationActivity = { reportDocumentInfo: sinon.stub() };
fakeAnnotationsService = { create: sinon.stub() };
fakeToastMessenger = new EventEmitter();
fakePortRPCs = [];
Expand Down Expand Up @@ -198,6 +213,7 @@ describe('FrameSyncService', () => {

frameSync = new Injector()
.register('$window', { value: fakeWindow })
.register('annotationActivity', { value: fakeAnnotationActivity })
.register('annotationsService', { value: fakeAnnotationsService })
.register('store', { value: fakeStore })
.register('toastMessenger', { value: fakeToastMessenger })
Expand Down Expand Up @@ -817,6 +833,35 @@ describe('FrameSyncService', () => {
assert.calledWith(channel.call, 'setHighlightsVisible', false);
});

describe("reporting the document's identity to the embedder", () => {
it('reports the main URI for an HTML document', async () => {
await connectGuest();
emitGuestEvent('documentInfoChanged', fixtures.htmlDocumentInfo);

assert.calledWith(
fakeAnnotationActivity.reportDocumentInfo,
fixtures.htmlDocumentInfo.uri,
);
});

it('reports the fingerprint URN for a PDF', async () => {
await connectGuest();
emitGuestEvent('documentInfoChanged', fixtures.pdfDocumentInfo);

assert.calledWith(
fakeAnnotationActivity.reportDocumentInfo,
'urn:x-pdf:FINGERPRINT',
);
});

it('does not report the identity of a non-main frame', async () => {
await connectGuest('test-frame');
emitGuestEvent('documentInfoChanged', fixtures.pdfDocumentInfo);

assert.notCalled(fakeAnnotationActivity.reportDocumentInfo);
});
});

[true, false].forEach(contentInfoAvailable => {
it('sends content info to guest if available', async () => {
let channel;
Expand Down
6 changes: 6 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ export type ConfigFromEmbedder = ConfigFromHost & {
*/
reportActivity?: ReportAnnotationActivityConfig;

/**
* Request that the loaded document's identity — be reported to the embedder frame via a
* `reportDocumentInfo` RPC call when the document loads.
*/
reportDocumentInfo?: boolean;

/** Configuration for menu items etc. related to LMS instructor dashboard */
dashboard?: DashboardConfig;

Expand Down
Loading