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
674e3aae
Unverified
Commit
674e3aae
authored
Sep 14, 2018
by
Robert Knight
Committed by
GitHub
Sep 14, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #767 from hypothesis/postmessage-json-rpc
Add client for making JSON-RPC calls over postMessage
parents
aa404bef
a957a1db
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
244 additions
and
0 deletions
+244
-0
postmessage-json-rpc.js
src/sidebar/util/postmessage-json-rpc.js
+91
-0
postmessage-json-rpc-test.js
src/sidebar/util/test/postmessage-json-rpc-test.js
+153
-0
No files found.
src/sidebar/util/postmessage-json-rpc.js
0 → 100644
View file @
674e3aae
'use strict'
;
const
{
generateHexString
}
=
require
(
'./random'
);
/** Generate a random ID to associate RPC requests and responses. */
function
generateId
()
{
return
generateHexString
(
10
);
}
/**
* Return a Promise that rejects with an error after `delay` ms.
*/
function
createTimeout
(
delay
,
message
)
{
return
new
Promise
((
_
,
reject
)
=>
{
setTimeout
(()
=>
reject
(
new
Error
(
message
)),
delay
);
});
}
/**
* Make a JSON-RPC call to a server in another frame using `postMessage`.
*
* @param {Window} frame - Frame to send call to
* @param {string} origin - Origin filter for `window.postMessage` call
* @param {string} method - Name of the JSON-RPC method
* @param {any[]} params - Parameters of the JSON-RPC method
* @param [number] timeout - Maximum time to wait in ms
* @param [Window] window_ - Test seam.
* @param [id] id - Test seam.
* @return {Promise<any>} - A Promise for the response to the call
*/
function
call
(
frame
,
origin
,
method
,
params
=
[],
timeout
=
2000
,
window_
=
window
,
id
=
generateId
())
{
// Send RPC request.
const
request
=
{
jsonrpc
:
'2.0'
,
method
,
params
,
id
,
};
try
{
frame
.
postMessage
(
request
,
origin
);
}
catch
(
err
)
{
return
Promise
.
reject
(
err
);
}
// Await response or timeout.
let
listener
;
const
response
=
new
Promise
((
resolve
,
reject
)
=>
{
listener
=
(
event
)
=>
{
if
(
event
.
origin
!==
origin
)
{
// Not from the frame that we sent the request to.
return
;
}
if
(
!
(
event
.
data
instanceof
Object
)
||
event
.
data
.
jsonrpc
!==
'2.0'
||
event
.
data
.
id
!==
id
)
{
// Not a valid JSON-RPC response.
return
;
}
const
{
error
,
result
}
=
event
.
data
;
if
(
error
!==
undefined
)
{
reject
(
error
);
}
else
if
(
result
!==
undefined
)
{
resolve
(
result
);
}
else
{
reject
(
new
Error
(
'RPC reply had no result or error'
));
}
};
window_
.
addEventListener
(
'message'
,
listener
);
});
const
timeoutExpired
=
createTimeout
(
timeout
,
`Request to
${
origin
}
timed out`
);
// Cleanup and return.
// FIXME: If we added a `Promise.finally` polyfill we could simplify this.
return
Promise
.
race
([
response
,
timeoutExpired
]).
then
(
result
=>
{
window_
.
removeEventListener
(
'message'
,
listener
);
return
result
;
}).
catch
(
err
=>
{
window_
.
removeEventListener
(
'message'
,
listener
);
throw
err
;
});
}
module
.
exports
=
{
call
,
};
src/sidebar/util/test/postmessage-json-rpc-test.js
0 → 100644
View file @
674e3aae
'use strict'
;
const
EventEmitter
=
require
(
'tiny-emitter'
);
const
{
call
}
=
require
(
'../postmessage-json-rpc'
);
class
FakeWindow
{
constructor
()
{
this
.
emitter
=
new
EventEmitter
;
this
.
addEventListener
=
this
.
emitter
.
on
.
bind
(
this
.
emitter
);
this
.
removeEventListener
=
this
.
emitter
.
off
.
bind
(
this
.
emitter
);
}
}
function
assertPromiseIsRejected
(
promise
,
expectedErr
)
{
const
rejectFlag
=
{};
return
promise
.
catch
(
err
=>
{
assert
.
equal
(
err
.
message
,
expectedErr
);
return
rejectFlag
;
}).
then
(
result
=>
{
assert
.
equal
(
result
,
rejectFlag
,
'expected promise to be rejected but it was fulfilled'
);
});
}
describe
(
'sidebar.util.postmessage-json-rpc'
,
()
=>
{
const
origin
=
'https://embedder.com'
;
const
messageId
=
42
;
describe
(
'call'
,
()
=>
{
let
frame
;
let
fakeWindow
;
function
doCall
()
{
const
timeout
=
1
;
return
call
(
frame
,
origin
,
'testMethod'
,
[
1
,
2
,
3
],
timeout
,
fakeWindow
,
messageId
);
}
beforeEach
(()
=>
{
frame
=
{
postMessage
:
sinon
.
stub
()
};
fakeWindow
=
new
FakeWindow
;
});
it
(
'sends a message to the target frame'
,
()
=>
{
doCall
().
catch
(()
=>
{}
/* Ignore timeout. */
);
assert
.
calledWith
(
frame
.
postMessage
,
{
jsonrpc
:
'2.0'
,
id
:
messageId
,
method
:
'testMethod'
,
params
:
[
1
,
2
,
3
],
});
});
it
(
'rejects if `postMessage` fails'
,
()
=>
{
frame
.
postMessage
.
throws
(
new
Error
(
'Nope!'
));
const
result
=
doCall
();
assertPromiseIsRejected
(
result
,
'Nope!'
);
});
[{
// Wrong origin.
origin
:
'https://not-the-embedder.com'
,
data
:
{
jsonrpc
:
'2.0'
,
id
:
messageId
,
},
},{
// Non-object `data` field.
origin
,
data
:
null
,
},{
// No jsonrpc header
origin
,
data
:
{},
},{
// No ID
origin
,
data
:
{
jsonrpc
:
'2.0'
,
},
},{
// ID mismatch
origin
,
data
:
{
jsonrpc
:
'2.0'
,
id
:
'wrong-id'
,
},
}].
forEach
(
reply
=>
{
it
(
'ignores messages that do not have required reply fields'
,
()
=>
{
const
result
=
doCall
();
fakeWindow
.
emitter
.
emit
(
'message'
,
reply
);
const
notCalled
=
Promise
.
resolve
(
'notcalled'
);
return
Promise
.
race
([
result
,
notCalled
]).
then
(
result
=>
{
assert
.
equal
(
result
,
'notcalled'
);
});
});
});
it
(
'rejects with an error if the `error` field is set in the response'
,
()
=>
{
const
result
=
doCall
();
fakeWindow
.
emitter
.
emit
(
'message'
,
{
origin
,
data
:
{
jsonrpc
:
'2.0'
,
id
:
messageId
,
error
:
{
message
:
'Something went wrong'
,
},
},
});
return
assertPromiseIsRejected
(
result
,
'Something went wrong'
);
});
it
(
'rejects if no `error` or `result` field is set in the response'
,
()
=>
{
const
result
=
doCall
();
fakeWindow
.
emitter
.
emit
(
'message'
,
{
origin
,
data
:
{
jsonrpc
:
'2.0'
,
id
:
messageId
},
});
return
assertPromiseIsRejected
(
result
,
'RPC reply had no result or error'
);
});
it
(
'resolves with the result if the `result` field is set in the response'
,
()
=>
{
const
result
=
doCall
();
const
expectedResult
=
{
foo
:
'bar'
};
fakeWindow
.
emitter
.
emit
(
'message'
,
{
origin
,
data
:
{
jsonrpc
:
'2.0'
,
id
:
messageId
,
result
:
expectedResult
,
},
});
return
result
.
then
(
result
=>
{
assert
.
deepEqual
(
result
,
expectedResult
);
});
});
it
(
'rejects with an error if the timeout is exceeded'
,
()
=>
{
const
result
=
doCall
();
return
assertPromiseIsRejected
(
result
,
'Request to https://embedder.com timed out'
);
});
});
});
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