Commit aa0039cb authored by Sheetal Umesh Kumar's avatar Sheetal Umesh Kumar Committed by GitHub

Merge pull request #550 from hypothesis/add-search-uris-postmessage

Send search URIs to other frames by postMessage()
parents 23163ab2 9ea6b7d4
......@@ -4,6 +4,7 @@ var addAnalytics = require('./ga');
var disableOpenerForExternalLinks = require('./util/disable-opener-for-external-links');
var getApiUrl = require('./get-api-url');
var serviceConfig = require('./service-config');
var crossOriginRPC = require('./cross-origin-rpc.js');
require('../shared/polyfills');
var raven;
......@@ -248,7 +249,8 @@ module.exports = angular.module('h', [
.config(configureRoutes)
.config(configureToastr)
.run(setupHttp);
.run(setupHttp)
.run(crossOriginRPC.server.start);
processAppOpts();
......
'use strict';
/**
* Begin responding to JSON-RPC requests from frames on other origins.
*
* Register a window.postMessage() event listener that receives and responds to
* JSON-RPC requests sent by frames on other origins using postMessage() as the
* transport layer.
*
* Only frames whose origin is in the rpcAllowedOrigins config setting will be
* responded to.
*
* This is a very partial implementation of a JSON-RPC 2.0 server:
*
* http://www.jsonrpc.org/specification
*
* The only part that we support so far is receiving JSON-RPC 2.0 requests (not
* notifications) without any parameters and sending back a successful
* response. Notifications (JSON-RPC calls that don't require a response),
* method parameters, and error responses are not yet supported.
*
*/
// @ngInject
function start(annotationUI, settings, $window) {
$window.addEventListener('message', function receiveMessage(event) {
let allowedOrigins = settings.rpcAllowedOrigins || [];
if (!allowedOrigins.includes(event.origin)) {
return;
}
// The entire JSON-RPC request object is contained in the postMessage()
// data param.
let jsonRpcRequest = event.data;
event.source.postMessage(jsonRpcResponse(jsonRpcRequest), event.origin);
});
/** Return a JSON-RPC response to the given JSON-RPC request object. */
function jsonRpcResponse(request) {
// The set of methods that clients can call.
let methods = {
'searchUris': annotationUI.searchUris,
};
let method = methods[request.method];
let response = {
'jsonrpc': '2.0',
'id': request.id,
};
if (method) {
response.result = method();
} else {
response.error = {
'code': -32601,
'message': 'Method not found',
};
}
return response;
}
}
module.exports = {
server: {
start: start,
},
};
'use strict';
var crossOriginRPC = require('../cross-origin-rpc');
describe('crossOriginRPC', function() {
describe('server', function() {
let addedListener; // The postMessage() listener that the server adds.
let fakeAnnotationUI;
let fakeWindow;
let settings;
let source;
beforeEach(function() {
fakeAnnotationUI = {
searchUris: sinon.stub().returns('THE_SEARCH_URIS'),
};
fakeWindow = {
addEventListener: sinon.stub().callsFake(function(type, listener) {
// Save the registered listener function in a variable so test code
// can access it later.
addedListener = listener;
}),
};
settings = {
rpcAllowedOrigins: ['https://allowed1.com', 'https://allowed2.com'],
};
source = { postMessage: sinon.stub() };
});
/**
* Directly call the postMessage() listener func that the server
* registered. This simulates what would happen if window.postMessage()
* were called.
*/
function postMessage(event) {
addedListener(event);
}
it('adds a postMessage() event listener function', function() {
crossOriginRPC.server.start(fakeAnnotationUI, {}, fakeWindow);
assert.isTrue(fakeWindow.addEventListener.calledOnce);
assert.isTrue(fakeWindow.addEventListener.calledWith('message'));
});
it('sends a response with the result from the called method', function() {
crossOriginRPC.server.start(fakeAnnotationUI, settings, fakeWindow);
postMessage({
data: { method: 'searchUris', id: 42 },
origin: 'https://allowed1.com',
source: source,
});
assert.isTrue(source.postMessage.calledOnce);
assert.isTrue(source.postMessage.calledWithExactly(
{
jsonrpc: '2.0',
id: 42,
result: 'THE_SEARCH_URIS',
},
'https://allowed1.com'
));
});
[
{},
{ rpcAllowedOrigins: [] },
{ rpcAllowedOrigins: ['https://allowed1.com', 'https://allowed2.com'] },
].forEach(function(settings) {
it("doesn't respond if the origin isn't allowed", function() {
crossOriginRPC.server.start(fakeAnnotationUI, settings, fakeWindow);
postMessage({
origin: 'https://notallowed.com',
data: { method: 'searchUris', id: 42 },
source: source,
});
assert.isTrue(source.postMessage.notCalled);
});
});
it("responds with an error if there's no method", function() {
crossOriginRPC.server.start(fakeAnnotationUI, settings, fakeWindow);
let jsonRpcRequest = { id: 42 }; // No "method" member.
postMessage({
origin: 'https://allowed1.com',
data: jsonRpcRequest,
source: source,
});
assert.isTrue(source.postMessage.calledOnce);
assert.isTrue(source.postMessage.calledWithExactly(
{
jsonrpc: '2.0',
id: 42,
error: {
code: -32601,
message: 'Method not found',
},
},
'https://allowed1.com'
));
});
[
'unknownMethod',
null,
].forEach(function(method) {
it('responds with an error if the method is unknown', function() {
crossOriginRPC.server.start(fakeAnnotationUI, settings, fakeWindow);
postMessage({
origin: 'https://allowed1.com',
data: { method: method, id: 42 },
source: source,
});
assert.isTrue(source.postMessage.calledOnce);
assert.isTrue(source.postMessage.calledWithExactly(
{
jsonrpc: '2.0',
id: 42,
error: {
code: -32601,
message: 'Method not found',
},
},
'https://allowed1.com'
));
});
});
});
});
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