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
98de0ad7
Unverified
Commit
98de0ad7
authored
Nov 08, 2018
by
Sean Hammond
Committed by
GitHub
Nov 08, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #805 from hypothesis/convert-discovery-to-js
Convert discovery.coffee to JS
parents
b42e5324
4122c69e
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
222 additions
and
157 deletions
+222
-157
cross-frame.coffee
src/annotator/plugin/cross-frame.coffee
+1
-1
discovery.coffee
src/shared/discovery.coffee
+0
-156
discovery.js
src/shared/discovery.js
+221
-0
No files found.
src/annotator/plugin/cross-frame.coffee
View file @
98de0ad7
...
...
@@ -67,7 +67,7 @@ module.exports = class CrossFrame extends Plugin
clientUrl
=
config
.
clientUrl
FrameUtil
.
isLoaded
frame
,
()
->
subFrameIdentifier
=
discovery
.
_
generateToken
()
subFrameIdentifier
=
discovery
.
generateToken
()
frameIdentifiers
.
set
(
frame
,
subFrameIdentifier
)
injectedConfig
=
Object
.
assign
({},
config
,
{
subFrameIdentifier
})
...
...
src/shared/discovery.coffee
deleted
100644 → 0
View file @
b42e5324
# A module for establishing connections between multiple frames in the same
# document. This model requires one frame (and only one) to be designated the
# server (created with options.server: true) which can then connect to as
# many clients as required. Once a handshake between two frames has been
# completed the onDiscovery callback will be called with information about
# both frames.
#
# Example:
#
# // host.html
# var server = new Discovery(window, {server: true})
# server.startDiscovery(function (window, source, token) {
# // Establish a message bus to the new client window.
# server.stopDiscovery();
# }
#
# // client.html
# var client = new Discovery(window)
# client.startDiscovery(function (window, source, token) {
# // Establish a message bus to the new server window.
# server.stopDiscovery();
# }
module
.
exports
=
class
Discovery
# Origins allowed to communicate on the channel
server
:
false
# When this is true, this bridge will act as a server and, similar to DHCP,
# offer to connect to bridges in other frames it discovers.
origin
:
'*'
onDiscovery
:
null
requestInProgress
:
false
# Accepts a target window and an object of options. The window provided will
# act as a starting point for discovering other windows.
constructor
:
(
@
target
,
options
=
{})
->
@
server
=
options
.
server
if
options
.
server
@
origin
=
options
.
origin
if
options
.
origin
startDiscovery
:
(
onDiscovery
)
->
if
@
onDiscovery
throw
new
Error
(
'Discovery is already in progress, call .stopDiscovery() first'
)
# Find other frames that run the same discovery mechanism. Sends a beacon
# and listens for beacons.
#
# Parameters:
# onDiscovery: (source, origin, token) -> ()
# When two frames discover each other, onDiscovery will be called on both
# sides with the same token string.
@
onDiscovery
=
onDiscovery
# Listen to discovery messages from other frames
@
target
.
addEventListener
(
'message'
,
this
.
_onMessage
,
false
)
# Send a discovery message to other frames to create channels
this
.
_beacon
()
return
stopDiscovery
:
=>
# Remove the listener for discovery messages
@
onDiscovery
=
null
@
target
.
removeEventListener
(
'message'
,
this
.
_onMessage
)
return
# Send out a beacon to discover frames to connect with
_beacon
:
->
beaconMessage
=
if
@
server
'__cross_frame_dhcp_offer'
else
'__cross_frame_dhcp_discovery'
# Starting at the top window, walk through all frames, and ping each frame
# that is not our own.
queue
=
[
@
target
.
top
]
while
queue
.
length
parent
=
queue
.
shift
()
if
parent
isnt
@
target
parent
.
postMessage
(
beaconMessage
,
@
origin
)
for
child
in
parent
.
frames
queue
.
push
(
child
)
return
_onMessage
:
(
event
)
=>
{
source
,
origin
,
data
}
=
event
# If `origin` is 'null' the source frame is a file URL or loaded over some
# other scheme for which the `origin` is undefined. In this case, the only
# way to ensure the message arrives is to use the wildcard origin. See:
#
# https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
#
# When sending messages to or from a Firefox WebExtension, current
# versions of Firefox have a bug that causes the origin check to fail even
# though the target and actual origins of the message match.
if
origin
is
'null'
||
origin
.
match
(
'moz-extension:'
)
||
window
.
location
.
protocol
==
'moz-extension:'
origin
=
'*'
# Check if the message is at all related to our discovery mechanism
match
=
data
.
match
?
/^__cross_frame_dhcp_(discovery|offer|request|ack)(?::(\d+))?$/
return
unless
match
# Read message type and optional token from message data
messageType
=
match
[
1
]
token
=
match
[
2
]
# Process the received message
{
reply
,
discovered
,
token
}
=
this
.
_processMessage
(
messageType
,
token
,
origin
)
if
reply
source
.
postMessage
'__cross_frame_dhcp_'
+
reply
,
origin
if
discovered
@
onDiscovery
.
call
(
null
,
source
,
origin
,
token
)
return
_processMessage
:
(
messageType
,
token
,
origin
)
->
# Process an incoming message, returns:
# - a reply message
# - whether the discovery has completed
reply
=
null
discovered
=
false
if
@
server
# We are configured as server
if
messageType
is
'discovery'
# A client joined the party. Offer it to connect.
reply
=
'offer'
else
if
messageType
is
'request'
# Create a channel with random identifier
token
=
this
.
_generateToken
()
reply
=
'ack'
+
':'
+
token
discovered
=
true
else
if
messageType
is
'offer'
or
messageType
is
'ack'
throw
new
Error
(
"""
A second Discovery server has been detected at
#{
origin
}
.
This is unsupported and will cause unexpected behaviour."""
)
else
# We are configured as a client
if
messageType
is
'offer'
# The server joined the party, or replied to our discovery message.
# Request it to set up a channel if we did not already do so.
unless
@
requestInProgress
@
requestInProgress
=
true
# prevent creating two channels
reply
=
'request'
else
if
messageType
is
'ack'
# The other side opened a channel to us. We note its scope and create
# a matching channel end on this side.
@
requestInProgress
=
false
# value should not actually matter anymore.
discovered
=
true
return
{
reply
:
reply
,
discovered
:
discovered
,
token
:
token
}
_generateToken
:
->
(
''
+
Math
.
random
()).
replace
(
/\D/g
,
''
)
src/shared/discovery.js
0 → 100644
View file @
98de0ad7
'use strict'
;
/**
* Callback invoked when another frame is discovered in this window which runs
* the Hypothesis sidebar or annotation layer code.
*
* @type {Function} DiscoveryCallback
* @param {Window} source - The frame that was discovered.
* @param {string} origin - The origin to use when posting messages to this frame.
* @param {string} token - A random identifier used by this frame.
*/
/**
* Discovery finds frames in the current tab/window that can be annotated (the
* "clients") or can fetch annotations from the backend (the "server").
*
* Currently only one frame can be designated as the server.
* (FIXME: This causes problems. See https://github.com/hypothesis/client/issues/249,
* https://github.com/hypothesis/client/issues/187).
*
* The discovery process works as follows:
*
* 1. Clients and servers perform a top-down, breadth-first traversal of the
* frame hierarchy in the tab and send either an "offer" (server) or
* "discovery" (client) message to each frame, except for their own frame.
* 2. Clients listen for "offer" messages and respond with "request" messages.
* 3. Servers listen for "discovery" messages and respond with "offer"
* messages.
* 4. Servers also listen for "request" messages and respond with "ack" messages
* that include a random channel identifier. At this point servers call
* the callback to `startDiscovery`.
* 5. Clients listen for "ack" messages. When they receive one from a server
* they call the callback to `startDiscovery`.
*/
class
Discovery
{
/**
* @param {Window} target
* @param {Object} options
*/
constructor
(
target
,
options
=
{})
{
/** The window to send and listen for messages with. */
this
.
target
=
target
;
/**
* Set whether this frame acts as a server (fetches annotations from the
* API) or a client (contains annotatable content and displays highlights).
*/
this
.
server
=
false
;
/** Origins allowed to communicate with this frame. */
this
.
origin
=
'*'
;
/**
* Flag set in client frames to indicate when they are waiting for a
* confirmation from a server frame.
*/
this
.
requestInProgress
=
false
;
this
.
onDiscovery
=
null
;
if
(
typeof
options
.
server
!==
'undefined'
)
{
this
.
server
=
options
.
server
;
}
if
(
typeof
options
.
origin
!==
'undefined'
)
{
this
.
origin
=
options
.
origin
;
}
this
.
_onMessage
=
this
.
_onMessage
.
bind
(
this
);
}
/**
* Find other frames to communicate with.
*
* See the class overview for a description of how the discovery process
* works.
*
* @param {DiscoveryCallback} onDiscovery - Callback to invoke with a token when
* another frame is discovered.
*/
startDiscovery
(
onDiscovery
)
{
if
(
this
.
onDiscovery
)
{
throw
new
Error
(
'Discovery is already in progress. Call stopDiscovery() first'
);
}
this
.
onDiscovery
=
onDiscovery
;
// Listen for messages from other frames.
this
.
target
.
addEventListener
(
'message'
,
this
.
_onMessage
,
false
);
this
.
_beacon
();
}
/**
* Stop listening for communication requests from other frames.
*/
stopDiscovery
()
{
this
.
onDiscovery
=
null
;
this
.
target
.
removeEventListener
(
'message'
,
this
.
_onMessage
);
}
/**
* Send a message to other frames in the current window to inform them about
* the existence of this frame and tell them whether this frame is a client
* or server.
*/
_beacon
()
{
let
beaconMessage
;
if
(
this
.
server
)
{
beaconMessage
=
'__cross_frame_dhcp_offer'
;
}
else
{
beaconMessage
=
'__cross_frame_dhcp_discovery'
;
}
// Perform a top-down, breadth-first traversal of frames in the current
// window and send messages to them.
const
queue
=
[
this
.
target
.
top
];
while
(
queue
.
length
>
0
)
{
const
parent
=
queue
.
shift
();
if
(
parent
!==
this
.
target
)
{
parent
.
postMessage
(
beaconMessage
,
this
.
origin
);
}
for
(
let
i
=
0
;
i
<
parent
.
frames
.
length
;
i
++
)
{
queue
.
push
(
parent
.
frames
[
i
]);
}
}
}
/**
* Handle a `MessageEvent` from another frame which _may_ be from a
* `Discovery` instance.
*/
_onMessage
(
event
)
{
const
{
source
,
data
}
=
event
;
let
origin
=
event
.
origin
;
// If `origin` is 'null' the source frame is a file URL or loaded over some
// other scheme for which the `origin` is undefined. In this case, the only
// way to ensure the message arrives is to use the wildcard origin. See:
//
// https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
//
// When sending messages to or from a Firefox WebExtension, current
// versions of Firefox have a bug that causes the origin check to fail even
// though the target and actual origins of the message match.
if
(
origin
===
'null'
||
origin
.
match
(
'moz-extension:'
)
||
window
.
location
.
protocol
===
'moz-extension:'
)
{
origin
=
'*'
;
}
// Check if this is a recognized message from a `Discovery` instance in
// another frame.
const
match
=
(
typeof
data
===
'string'
&&
data
.
match
(
/^__cross_frame_dhcp_
(
discovery|offer|request|ack
)(?:
:
(\d
+
))?
$/
)
);
if
(
!
match
)
{
return
;
}
// Handle the message, and send a response back to the original frame if
// appropriate.
let
[
,
messageType
,
messageToken
]
=
match
;
const
{
reply
,
discovered
,
token
}
=
this
.
_processMessage
(
messageType
,
messageToken
,
origin
);
if
(
reply
)
{
source
.
postMessage
(
'__cross_frame_dhcp_'
+
reply
,
origin
);
}
// Notify caller of `startDiscovery` in this frame that we found another
// frame.
if
(
discovered
)
{
this
.
onDiscovery
.
call
(
null
,
source
,
origin
,
token
);
}
}
_processMessage
(
messageType
,
token
,
origin
)
{
let
reply
=
null
;
let
discovered
=
false
;
if
(
this
.
server
)
{
// Handle message as a server frame.
if
(
messageType
===
'discovery'
)
{
reply
=
'offer'
;
}
else
if
(
messageType
===
'request'
)
{
token
=
this
.
generateToken
();
reply
=
`ack:
${
token
}
`
;
discovered
=
true
;
}
else
if
(
messageType
===
'offer'
||
messageType
===
'ack'
)
{
throw
new
Error
(
`A second Discovery server has been detected at
${
origin
}
.
This is unsupported and will cause unexpected behaviour.`
);
}
}
else
{
// Handle message as a client frame.
if
(
messageType
===
'offer'
)
{
// eslint-disable-line no-lonely-if
if
(
!
this
.
requestInProgress
)
{
this
.
requestInProgress
=
true
;
reply
=
'request'
;
}
}
else
if
(
messageType
===
'ack'
)
{
this
.
requestInProgress
=
false
;
discovered
=
true
;
}
}
return
{
reply
,
discovered
,
token
};
}
/**
* Generate a random identifier for a communication channel between a client
* and a server.
*/
generateToken
()
{
return
Math
.
random
().
toString
().
replace
(
/
\D
/g
,
''
);
}
}
module
.
exports
=
Discovery
;
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