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
00c59e0e
Commit
00c59e0e
authored
Nov 09, 2021
by
Eduardo Sanz García
Committed by
Eduardo
Nov 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Apply suggestions from code review
Co-authored-by:
Robert Knight
<
robertknight@gmail.com
>
parent
e78ada77
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
296 additions
and
321 deletions
+296
-321
port-finder.js
src/shared/port-finder.js
+48
-102
port-provider.js
src/shared/port-provider.js
+127
-128
port-util.js
src/shared/port-util.js
+3
-1
port-finder-test.js
src/shared/test/port-finder-test.js
+80
-77
port-provider-test.js
src/shared/test/port-provider-test.js
+38
-13
No files found.
src/shared/port-finder.js
View file @
00c59e0e
...
@@ -2,7 +2,7 @@ import { ListenerCollection } from './listener-collection';
...
@@ -2,7 +2,7 @@ import { ListenerCollection } from './listener-collection';
import
{
isMessageEqual
,
SOURCE
as
source
}
from
'./port-util'
;
import
{
isMessageEqual
,
SOURCE
as
source
}
from
'./port-util'
;
const
MAX_WAIT_FOR_PORT
=
1000
*
30
;
const
MAX_WAIT_FOR_PORT
=
1000
*
30
;
const
POLLING_INTERVAL_FOR_PORT
=
50
0
;
const
POLLING_INTERVAL_FOR_PORT
=
25
0
;
/**
/**
* @typedef {import('../types/annotator').Destroyable} Destroyable
* @typedef {import('../types/annotator').Destroyable} Destroyable
...
@@ -12,8 +12,7 @@ const POLLING_INTERVAL_FOR_PORT = 500;
...
@@ -12,8 +12,7 @@ const POLLING_INTERVAL_FOR_PORT = 500;
*/
*/
/**
/**
* PortFinder class should be used in frames that are not the `host` frame. It
* PortFinder helps to discover `MessagePort` on a specific channel.
* helps to discover `MessagePort` on a specific channel.
*
*
* Channel nomenclature is `[frame1]-[frame2]` so that:
* Channel nomenclature is `[frame1]-[frame2]` so that:
* - `port1` should be owned by/transferred to `frame1`, and
* - `port1` should be owned by/transferred to `frame1`, and
...
@@ -28,118 +27,65 @@ export class PortFinder {
...
@@ -28,118 +27,65 @@ export class PortFinder {
this
.
_listeners
=
new
ListenerCollection
();
this
.
_listeners
=
new
ListenerCollection
();
}
}
// Two important characteristics of `MessagePort`:
destroy
()
{
// - it can only be used by one frame; the port is neutered if, after started to
this
.
_listeners
.
removeAll
();
// be used to receive messages, the port is transferred to a different frame.
}
// - messages are queued until the other port is ready to listen (`port.start()`)
/**
/**
* `guest-host` communication
* Polls the hostFrame for a specific port and returns a Promise of the port.
* @typedef {{channel: 'guest-host', hostFrame: Window, port: 'guest'}} options0
*
* `guest-sidebar` communication
* @typedef {{channel: 'guest-sidebar', hostFrame: Window, port: 'guest'}} options1
*
*
* `host-sidebar` communication
* @param {object} options
* @typedef {{channel: 'host-sidebar', hostFrame: Window, port: 'sidebar'}} options2
* @param {Channel} options.channel - requested channel
*
* @param {Window} options.hostFrame - frame where the hypothesis client is
* `notebook-sidebar` communication
* loaded and `PortProvider` is listening for messages
* @typedef {{channel: 'notebook-sidebar', hostFrame: Window, port: 'notebook'}} options3
* @param {Port} options.port - requested port
*
* @param {options0|options1|options2|options3} options
* @return {Promise<MessagePort>}
* @return {Promise<MessagePort>}
*/
*/
discover
(
options
)
{
discover
({
channel
,
hostFrame
,
port
})
{
const
{
channel
,
port
}
=
options
;
let
isValidRequest
=
false
;
if
(
(
channel
===
'guest-host'
&&
port
===
'guest'
)
||
(
channel
===
'guest-sidebar'
&&
port
===
'guest'
)
||
(
channel
===
'host-sidebar'
&&
port
===
'sidebar'
)
||
(
channel
===
'notebook-sidebar'
&&
port
===
'notebook'
)
)
{
isValidRequest
=
true
;
}
return
new
Promise
((
resolve
,
reject
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
if
(
!
isValidRequest
)
{
(
channel
===
'guest-host'
&&
port
===
'guest'
)
||
reject
(
new
Error
(
'Invalid request of channel/port'
));
(
channel
===
'guest-sidebar'
&&
port
===
'guest'
)
||
(
channel
===
'host-sidebar'
&&
port
===
'sidebar'
)
||
(
channel
===
'notebook-sidebar'
&&
port
===
'notebook'
)
)
{
this
.
_requestPort
({
...
options
,
reject
,
resolve
,
});
return
;
return
;
}
}
reject
(
new
Error
(
'Invalid request of channel/port'
));
function
postRequest
()
{
});
hostFrame
.
postMessage
({
channel
,
port
,
source
,
type
:
'request'
},
'*'
);
}
}
/**
* @typedef RequestPortOptions
* @prop {Channel} channel - requested channel
* @prop {Window} hostFrame - the frame where the hypothesis client is loaded.
* It is used to send a `window.postMessage`.
* @prop {Port} port - requested port
* @prop {(reason: Error) => void} reject - execute the `Promise.reject` in case
* the `host` frame takes too long to answer the request.
* @prop {(port: MessagePort) => void} resolve - execute the `Promise.resolve`
* when `host` frame successfully answers the request.
*/
/**
* Register a listener for the port `offer` and sends a request for one port.
*
* @param {RequestPortOptions} options
*/
_requestPort
({
channel
,
hostFrame
,
port
,
reject
,
resolve
})
{
function
postRequest
()
{
hostFrame
.
postMessage
({
channel
,
port
,
source
,
type
:
'request'
},
'*'
);
}
const
intervalId
=
window
.
setInterval
(
()
=>
postRequest
(),
POLLING_INTERVAL_FOR_PORT
);
// The `host` frame maybe busy, that's why we should wait.
const
intervalId
=
setInterval
(
const
timeoutId
=
window
.
setTimeout
(()
=>
{
()
=>
postRequest
(),
clearInterval
(
intervalId
);
POLLING_INTERVAL_FOR_PORT
reject
(
new
Error
(
`Unable to find '
${
port
}
' port on '
${
channel
}
' channel`
)
);
);
},
MAX_WAIT_FOR_PORT
);
// TODO: It would be nice to remove the listener after receiving the port.
this
.
_listeners
.
add
(
window
,
'message'
,
event
=>
this
.
_handlePortOffer
(
/** @type {MessageEvent} */
(
event
),
{
intervalId
,
message
:
{
channel
,
port
,
source
,
type
:
'offer'
},
resolve
,
timeoutId
,
})
);
postRequest
();
// The `host` frame maybe busy, that's why we should wait.
}
const
timeoutId
=
window
.
setTimeout
(()
=>
{
clearInterval
(
intervalId
);
reject
(
new
Error
(
`Unable to find '
${
port
}
' port on '
${
channel
}
' channel`
)
);
},
MAX_WAIT_FOR_PORT
);
/**
// TODO: It would be nice to remove the listener after receiving the port.
* Resolve with a MessagePort when the `offer` message matches.
this
.
_listeners
.
add
(
window
,
'message'
,
event
=>
{
*
const
{
data
,
ports
}
=
/** @type {MessageEvent} */
(
event
);
* @param {MessageEvent} event
if
(
isMessageEqual
(
data
,
{
channel
,
port
,
source
,
type
:
'offer'
}))
{
* @param {object} options
clearInterval
(
intervalId
);
* @param {Message} options.message
clearTimeout
(
timeoutId
);
* @param {(port: MessagePort) => void} options.resolve
resolve
(
ports
[
0
]);
* @param {number} options.timeoutId
}
* @param {number} [options.intervalId]
});
*/
_handlePortOffer
(
{
data
,
ports
},
{
message
,
resolve
,
timeoutId
,
intervalId
}
)
{
if
(
isMessageEqual
(
data
,
message
))
{
clearInterval
(
intervalId
);
clearTimeout
(
timeoutId
);
resolve
(
ports
[
0
]);
}
}
destroy
()
{
postRequest
();
this
.
_listeners
.
removeAll
(
);
}
);
}
}
}
}
src/shared/port-provider.js
View file @
00c59e0e
import
{
TinyEmitter
}
from
'tiny-emitter'
;
import
{
ListenerCollection
}
from
'./listener-collection'
;
import
{
ListenerCollection
}
from
'./listener-collection'
;
import
{
isMessageEqual
,
SOURCE
as
source
}
from
'./port-util'
;
import
{
isMessageEqual
,
SOURCE
as
source
}
from
'./port-util'
;
...
@@ -38,7 +40,7 @@ import { isMessageEqual, SOURCE as source } from './port-util';
...
@@ -38,7 +40,7 @@ import { isMessageEqual, SOURCE as source } from './port-util';
* - `host-sidebar`
* - `host-sidebar`
* - `notebook-sidebar`
* - `notebook-sidebar`
*
*
* `PortProvider` is used only
on the `host` frame. The rest of the
frames use the
* `PortProvider` is used only
in the `host` frame. The other
frames use the
* companion class, `PortFinder`. `PortProvider` creates a `MessageChannel`
* companion class, `PortFinder`. `PortProvider` creates a `MessageChannel`
* for two frames to communicate with each other. It also listens to requests for
* for two frames to communicate with each other. It also listens to requests for
* particular `MessagePort` and dispatches the corresponding `MessagePort`.
* particular `MessagePort` and dispatches the corresponding `MessagePort`.
...
@@ -55,7 +57,14 @@ import { isMessageEqual, SOURCE as source } from './port-util';
...
@@ -55,7 +57,14 @@ import { isMessageEqual, SOURCE as source } from './port-util';
* V
* V
* 5. send reciprocal port to the `sidebar` frame using the `host-sidebar`
* 5. send reciprocal port to the `sidebar` frame using the `host-sidebar`
*
*
* Channel nomenclature is `[frame1]-[frame2]` so that:
* - `port1` should be owned by/transferred to `frame1`, and
* - `port2` should be owned by/transferred to `frame2`
*
*
* @implements Destroyable
*/
/*
* In some situations, because `guest` iframe/s load in parallel to the `host`
* In some situations, because `guest` iframe/s load in parallel to the `host`
* frame, we can not assume that the code in the `host` frame is executed before
* frame, we can not assume that the code in the `host` frame is executed before
* the code in a `guest` frame. Hence, we can't assume that `PortProvider` (in
* the code in a `guest` frame. Hence, we can't assume that `PortProvider` (in
...
@@ -63,23 +72,27 @@ import { isMessageEqual, SOURCE as source } from './port-util';
...
@@ -63,23 +72,27 @@ import { isMessageEqual, SOURCE as source } from './port-util';
* Therefore, for the `PortFinder`, we implement a polling strategy (sending a
* Therefore, for the `PortFinder`, we implement a polling strategy (sending a
* message every N milliseconds) until a response is received.
* message every N milliseconds) until a response is received.
*
*
* Channel nomenclature is `[frame1]-[frame2]` so that:
* Two important characteristics of `MessagePort`:
* - `port1` should be owned by/transferred to `frame1`, and
* - it can only be used by one frame: in Chrome the port is neutered if transferred twice
* - `port2` should be owned by/transferred to `frame2`
* - messages are queued until the other port is ready to listen (`port.start()`)
*
* @implements Destroyable
*/
*/
export
class
PortProvider
{
export
class
PortProvider
{
/**
/**
* @param {string} hypothesisAppsURL
* @param {string} hypothesisAppsOrigin - the origin of the hypothesis apps
* is use to send the notebook and sidebar ports to only the frames that
* matches the origin.
*/
*/
constructor
(
hypothesisAppsURL
)
{
constructor
(
hypothesisAppsOrigin
)
{
this
.
_hypothesisAppsOrigin
=
new
URL
(
hypothesisAppsURL
).
origin
;
this
.
_hypothesisAppsOrigin
=
hypothesisAppsOrigin
;
this
.
_emitter
=
new
TinyEmitter
();
// Although some channels (v.gr. `notebook-sidebar`) have only one
// Although some channels (v.gr. `notebook-sidebar`) have only one
// `MessageChannel`, other channels (v.gr. `guest-sidebar`) can have multiple
// `MessageChannel`, other channels (v.gr. `guest-sidebar`) can have multiple
// `MessageChannel`s. In spite of the number channel, we store all
// `MessageChannel`s. In spite of the number channel, we store all
// `MessageChannel` on a `Map<Window, MessageChannel>`.
// `MessageChannel` on a `Map<Window, MessageChannel>`. The `Window` refers
// to the frame that sends the initial request that triggers creation of a
// channel.
/** @type {Map<Channel, Map<Window, MessageChannel>>} */
/** @type {Map<Channel, Map<Window, MessageChannel>>} */
this
.
_channels
=
new
Map
();
this
.
_channels
=
new
Map
();
...
@@ -88,90 +101,19 @@ export class PortProvider {
...
@@ -88,90 +101,19 @@ export class PortProvider {
this
.
_hostSidebarChannel
=
new
MessageChannel
();
this
.
_hostSidebarChannel
=
new
MessageChannel
();
this
.
_listeners
=
new
ListenerCollection
();
this
.
_listeners
=
new
ListenerCollection
();
this
.
_listen
();
}
/**
* @param {'onHostPortRequest'} _eventName
* @param {(MessagePort, channel: 'guest') => void} handler - this handler
* fires when a request for the 'guest-host' channel is listened.
*/
addEventListener
(
_eventName
,
handler
)
{
this
.
_onHostPortRequest
=
handler
;
}
/**
* Returns a port from a channel. Currently, only returns the `host` port from
* the `host-Sidebar` channel. Otherwise, it returns `null`.
*
* @param {object} options
* @param {'host-sidebar'} options.channel
* @param {'host'} options.port
*/
getPort
({
channel
,
port
})
{
if
(
channel
===
'host-sidebar'
&&
port
===
'host'
)
{
return
this
.
_hostSidebarChannel
.
port1
;
}
return
null
;
}
/**
* Initiate the listener of port requests by other frames.
*/
_listen
()
{
/** @type {Array<{allowedOrigin: string, channel: Channel, port: Port}>} */
([
{
allowedOrigin
:
'*'
,
channel
:
'guest-host'
,
port
:
'guest'
,
},
{
allowedOrigin
:
'*'
,
channel
:
'guest-sidebar'
,
port
:
'guest'
,
},
{
allowedOrigin
:
this
.
_hypothesisAppsOrigin
,
channel
:
'host-sidebar'
,
port
:
'sidebar'
,
},
{
allowedOrigin
:
this
.
_hypothesisAppsOrigin
,
channel
:
'notebook-sidebar'
,
port
:
'notebook'
,
},
]).
forEach
(({
allowedOrigin
,
channel
,
port
})
=>
{
this
.
_listeners
.
add
(
window
,
'message'
,
event
=>
this
.
_handlePortRequest
(
/** @type {MessageEvent} */
(
event
),
{
allowedMessage
:
{
channel
,
port
,
source
,
type
:
'request'
,
},
allowedOrigin
,
})
);
});
}
}
/**
* @typedef Options
* @prop {Message} allowedMessage - the request `MessageEvent` must match this
* object to grant the port.
* @prop {string} allowedOrigin - the origin in the `MessageEvent` must match
* this value to grant the port. If '*' allow all origins.
*/
/**
/**
* Checks the `postMessage` origin and message.
* Checks the `postMessage` origin and message.
*
*
* @param {MessageEvent} event
* @param {MessageEvent} event
* @param {Options} options
* @param {Message} allowedMessage - the MessageEvent's data must match this
* object to grant the port.
* @param {string} allowedOrigin - the MessageEvent's origin must match this
* value to grant the port. If '*' allow all origins.
*/
*/
_isValidRequest
({
data
,
origin
,
source
},
{
allowedMessage
,
allowedOrigin
})
{
_isValidRequest
(
event
,
allowedMessage
,
allowedOrigin
)
{
const
{
data
,
origin
,
source
}
=
event
;
if
(
allowedOrigin
!==
'*'
&&
origin
!==
allowedOrigin
)
{
if
(
allowedOrigin
!==
'*'
&&
origin
!==
allowedOrigin
)
{
return
false
;
return
false
;
}
}
...
@@ -195,16 +137,17 @@ export class PortProvider {
...
@@ -195,16 +137,17 @@ export class PortProvider {
}
}
/**
/**
* Send (1) the requested port via `frame#postMessage` (the origin is set
* Send a message and a port to the corresponding destinations.
* to match the allowedOrigin) and (2) the reciprocal port, if one is provided,
* to the `sidebar` frame using `host-sidebar(channel).host(port)#postMessage`
*
*
* @param {MessageEvent} event
* @param {MessageEvent} event
* @param {Options & {port: MessagePort, reciprocalPort? : MessagePort}} options
* @param {Message} message - the message to be sent.
* @param {MessagePort} port - the port to be sent via `window#postMessage`
* (the origin is set to match the MessageEvent's origin)frame that sends the initial request th
* @param {MessagePort} [reciprocalPort] - if a reciprocal port is provided,
* send this port (1) to the `sidebar` frame using the `host-sidebar`
* channel or (2) through the `onHostPortRequest` event listener.
*/
*/
_sendPort
(
event
,
{
allowedMessage
,
port
,
reciprocalPort
})
{
_sendPort
(
event
,
message
,
port
,
reciprocalPort
)
{
const
message
=
{
...
allowedMessage
,
type
:
'offer'
};
const
source
=
/** @type {Window} */
(
event
.
source
);
const
source
=
/** @type {Window} */
(
event
.
source
);
source
.
postMessage
(
message
,
event
.
origin
,
[
port
]);
source
.
postMessage
(
message
,
event
.
origin
,
[
port
]);
...
@@ -214,54 +157,110 @@ export class PortProvider {
...
@@ -214,54 +157,110 @@ export class PortProvider {
this
.
_hostSidebarChannel
.
port1
.
postMessage
(
message
,
[
reciprocalPort
]);
this
.
_hostSidebarChannel
.
port1
.
postMessage
(
message
,
[
reciprocalPort
]);
}
}
if
(
message
.
channel
===
'guest-host'
&&
message
.
port
===
'guest'
)
{
if
(
message
.
channel
===
'guest-host'
&&
message
.
port
===
'guest'
)
{
this
.
_
onHostPortRequest
?.(
reciprocalPort
,
message
.
port
);
this
.
_
emitter
.
emit
(
'hostPortRequest'
,
reciprocalPort
,
message
.
port
);
}
}
}
}
}
}
/**
/**
*
Respond to request of ports on channels.
*
@param {'hostPortRequest'} eventName
* @param {
MessageEvent} event
* @param {
(MessagePort, channel: 'guest') => void} handler - this handler
*
@param {Options} options
*
fires when a request for the 'guest-host' channel is listened.
*/
*/
_handlePortRequest
(
event
,
options
)
{
on
(
eventName
,
handler
)
{
if
(
!
this
.
_isValidRequest
(
event
,
options
))
{
this
.
_emitter
.
on
(
eventName
,
handler
);
return
;
}
/**
* Returns a port from a channel. Currently, only returns the `host` port from
* the `host-Sidebar` channel. Otherwise, it returns `null`.
*
* @param {object} options
* @param {'host-sidebar'} options.channel
* @param {'host'} options.port
*/
getPort
({
channel
,
port
})
{
if
(
channel
===
'host-sidebar'
&&
port
===
'host'
)
{
return
this
.
_hostSidebarChannel
.
port1
;
}
}
const
{
channel
}
=
options
.
allowedMessage
;
return
null
;
}
let
windowChannelMap
=
this
.
_channels
.
get
(
channel
);
/**
if
(
!
windowChannelMap
)
{
* Initiate the listener of port requests by other frames.
windowChannelMap
=
new
Map
();
*/
this
.
_channels
.
set
(
channel
,
windowChannelMap
);
listen
()
{
}
this
.
_listeners
.
add
(
window
,
'message'
,
messageEvent
=>
{
const
event
=
/** @type {MessageEvent} */
(
messageEvent
);
/** @type {Array<{allowedOrigin: string, channel: Channel, port: Port}>} */
([
{
allowedOrigin
:
'*'
,
channel
:
'guest-host'
,
port
:
'guest'
,
},
{
allowedOrigin
:
'*'
,
channel
:
'guest-sidebar'
,
port
:
'guest'
,
},
{
allowedOrigin
:
this
.
_hypothesisAppsOrigin
,
channel
:
'host-sidebar'
,
port
:
'sidebar'
,
},
{
allowedOrigin
:
this
.
_hypothesisAppsOrigin
,
channel
:
'notebook-sidebar'
,
port
:
'notebook'
,
},
]).
forEach
(({
allowedOrigin
,
channel
,
port
})
=>
{
/** @type {Message} */
const
allowedMessage
=
{
channel
,
port
,
source
,
type
:
'request'
,
};
const
source
=
/** @type {Window} */
(
event
.
source
);
if
(
!
this
.
_isValidRequest
(
event
,
allowedMessage
,
allowedOrigin
))
{
let
messageChannel
=
windowChannelMap
.
get
(
source
);
return
;
}
// Ignore the port request if the channel for the specified window has
let
windowChannelMap
=
this
.
_channels
.
get
(
channel
);
// already been created. This is to avoid transfer the port more than once.
if
(
!
windowChannelMap
)
{
if
(
messageChannel
)
{
windowChannelMap
=
new
Map
();
return
;
this
.
_channels
.
set
(
channel
,
windowChannelMap
)
;
}
}
// `host-sidebar` channel is an special case, because it is created in the
const
eventSource
=
/** @type {Window} */
(
event
.
source
);
// constructor.
let
messageChannel
=
windowChannelMap
.
get
(
eventSource
);
if
(
channel
===
'host-sidebar'
)
{
windowChannelMap
.
set
(
source
,
this
.
_hostSidebarChannel
);
// Ignore the port request if the channel for the specified window has
this
.
_sendPort
(
event
,
{
// already been created. This is to avoid transfer the port more than once.
...
options
,
if
(
messageChannel
)
{
port
:
this
.
_hostSidebarChannel
.
port2
,
return
;
});
}
return
;
}
messageChannel
=
new
MessageChannel
();
/** @type {Message} */
windowChannelMap
.
set
(
source
,
messageChannel
)
;
const
message
=
{
...
allowedMessage
,
type
:
'offer'
}
;
const
{
port1
,
port2
}
=
messageChannel
;
// `host-sidebar` channel is an special case, because it is created in the
this
.
_sendPort
(
event
,
{
...
options
,
port
:
port1
,
reciprocalPort
:
port2
});
// constructor.
if
(
channel
===
'host-sidebar'
)
{
windowChannelMap
.
set
(
eventSource
,
this
.
_hostSidebarChannel
);
this
.
_sendPort
(
event
,
message
,
this
.
_hostSidebarChannel
.
port2
);
return
;
}
messageChannel
=
new
MessageChannel
();
windowChannelMap
.
set
(
eventSource
,
messageChannel
);
const
{
port1
,
port2
}
=
messageChannel
;
this
.
_sendPort
(
event
,
message
,
port1
,
port2
);
});
});
}
}
destroy
()
{
destroy
()
{
...
...
src/shared/port-util.js
View file @
00c59e0e
// Because there are many `postMessages` on the `host` frame, the SOURCE property
// Because there are many `postMessages` on the `host` frame, the SOURCE property
// is added to the hypothesis `postMessages` to identify the provenance of the
// is added to the hypothesis `postMessages` to identify the provenance of the
// message and avoid listening to messages that could have the same properties
// message and avoid listening to messages that could have the same properties
// but different source. This is not a
is not a
security feature but an
// but different source. This is not a security feature but an
// anti-collision mechanism.
// anti-collision mechanism.
export
const
SOURCE
=
'hypothesis'
;
export
const
SOURCE
=
'hypothesis'
;
/**
/**
* These types are the used in by `PortProvider` and `PortFinder` for the
* inter-frame discovery and communication processes.
* @typedef {'guest-host'|'guest-sidebar'|'host-sidebar'|'notebook-sidebar'} Channel
* @typedef {'guest-host'|'guest-sidebar'|'host-sidebar'|'notebook-sidebar'} Channel
* @typedef {'guest'|'host'|'notebook'|'sidebar'} Port
* @typedef {'guest'|'host'|'notebook'|'sidebar'} Port
*
*
...
...
src/shared/test/port-finder-test.js
View file @
00c59e0e
...
@@ -26,92 +26,93 @@ describe('PortFinder', () => {
...
@@ -26,92 +26,93 @@ describe('PortFinder', () => {
portFinder
.
destroy
();
portFinder
.
destroy
();
});
});
[
describe
(
'#destroy'
,
()
=>
{
{
channel
:
'invalid'
,
port
:
'guest'
},
it
(
'ignores `offer` messages of ports'
,
async
()
=>
{
{
channel
:
'guest-host'
,
port
:
'invalid'
},
].
forEach
(({
channel
,
port
})
=>
it
(
'rejects if requesting an invalid port'
,
async
()
=>
{
let
error
;
let
error
;
const
channel
=
'host-sidebar'
;
const
port
=
'sidebar'
;
const
{
port1
}
=
new
MessageChannel
();
const
clock
=
sinon
.
useFakeTimers
();
try
{
try
{
await
portFinder
.
discover
({
portFinder
channel
,
.
discover
({
hostFrame
:
window
,
channel
,
port
,
hostFrame
:
window
,
port
,
})
.
catch
(
e
=>
(
error
=
e
));
portFinder
.
destroy
();
sendMessage
({
data
:
{
channel
,
port
,
source
,
type
:
'offer'
},
ports
:
[
port1
],
});
});
}
catch
(
e
)
{
clock
.
tick
(
30000
);
error
=
e
;
}
finally
{
clock
.
restore
();
}
}
assert
.
equal
(
error
.
message
,
'Invalid request of channel/port'
);
})
);
[
{
channel
:
'guest-host'
,
port
:
'guest'
},
{
channel
:
'guest-sidebar'
,
port
:
'guest'
},
{
channel
:
'host-sidebar'
,
port
:
'sidebar'
},
{
channel
:
'notebook-sidebar'
,
port
:
'notebook'
},
].
forEach
(({
channel
,
port
})
=>
it
(
'resolves if requesting a valid port'
,
async
()
=>
{
const
{
port1
}
=
new
MessageChannel
();
let
resolvedPort
;
portFinder
.
discover
({
channel
,
hostFrame
:
window
,
port
,
})
.
then
(
port
=>
(
resolvedPort
=
port
));
sendMessage
({
data
:
{
channel
,
port
,
source
,
type
:
'offer'
},
ports
:
[
port1
],
});
await
delay
(
0
);
await
delay
(
0
);
assert
.
instanceOf
(
resolvedPort
,
MessagePort
);
assert
.
equal
(
})
error
.
message
,
);
"Unable to find 'sidebar' port on 'host-sidebar' channel"
);
it
(
"timeouts if host doesn't respond"
,
async
()
=>
{
});
let
error
;
});
const
channel
=
'host-sidebar'
;
const
port
=
'sidebar'
;
describe
(
'#discover'
,
()
=>
{
const
clock
=
sinon
.
useFakeTimers
();
[
{
channel
:
'invalid'
,
port
:
'guest'
},
try
{
{
channel
:
'guest-host'
,
port
:
'invalid'
},
portFinder
{
channel
:
'guest-host'
,
port
:
'host'
},
.
discover
({
].
forEach
(({
channel
,
port
})
=>
channel
,
it
(
'rejects if requesting an invalid port'
,
async
()
=>
{
hostFrame
:
window
,
let
error
;
port
,
try
{
})
await
portFinder
.
discover
({
.
catch
(
e
=>
(
error
=
e
));
channel
,
clock
.
tick
(
30000
);
hostFrame
:
window
,
}
finally
{
port
,
clock
.
restore
();
});
}
}
catch
(
e
)
{
error
=
e
;
assert
.
callCount
(
window
.
postMessage
,
61
);
}
assert
.
alwaysCalledWithExactly
(
assert
.
equal
(
error
.
message
,
'Invalid request of channel/port'
);
window
.
postMessage
,
})
{
channel
,
port
,
source
,
type
:
'request'
},
'*'
);
);
await
delay
(
0
);
[
{
channel
:
'guest-host'
,
port
:
'guest'
},
{
channel
:
'guest-sidebar'
,
port
:
'guest'
},
{
channel
:
'host-sidebar'
,
port
:
'sidebar'
},
{
channel
:
'notebook-sidebar'
,
port
:
'notebook'
},
].
forEach
(({
channel
,
port
})
=>
it
(
'resolves if requesting a valid port'
,
async
()
=>
{
const
{
port1
}
=
new
MessageChannel
();
let
resolvedPort
;
assert
.
equal
(
portFinder
error
.
message
,
.
discover
({
"Unable to find 'sidebar' port on 'host-sidebar' channel"
channel
,
hostFrame
:
window
,
port
,
})
.
then
(
port
=>
(
resolvedPort
=
port
));
sendMessage
({
data
:
{
channel
,
port
,
source
,
type
:
'offer'
},
ports
:
[
port1
],
});
await
delay
(
0
);
assert
.
instanceOf
(
resolvedPort
,
MessagePort
);
})
);
);
});
describe
(
'#destroy'
,
()
=>
{
it
(
"timeouts if host doesn't respond"
,
async
()
=>
{
it
(
'ignores `offer` messages of ports'
,
async
()
=>
{
let
error
;
let
error
;
const
channel
=
'host-sidebar'
;
const
channel
=
'host-sidebar'
;
const
port
=
'sidebar'
;
const
port
=
'sidebar'
;
const
{
port1
}
=
new
MessageChannel
();
const
clock
=
sinon
.
useFakeTimers
();
const
clock
=
sinon
.
useFakeTimers
();
try
{
try
{
...
@@ -122,16 +123,18 @@ describe('PortFinder', () => {
...
@@ -122,16 +123,18 @@ describe('PortFinder', () => {
port
,
port
,
})
})
.
catch
(
e
=>
(
error
=
e
));
.
catch
(
e
=>
(
error
=
e
));
portFinder
.
destroy
();
sendMessage
({
data
:
{
channel
,
port
,
source
,
type
:
'offer'
},
ports
:
[
port1
],
});
clock
.
tick
(
30000
);
clock
.
tick
(
30000
);
}
finally
{
}
finally
{
clock
.
restore
();
clock
.
restore
();
}
}
assert
.
callCount
(
window
.
postMessage
,
121
);
assert
.
alwaysCalledWithExactly
(
window
.
postMessage
,
{
channel
,
port
,
source
,
type
:
'request'
},
'*'
);
await
delay
(
0
);
await
delay
(
0
);
assert
.
equal
(
assert
.
equal
(
...
...
src/shared/test/port-provider-test.js
View file @
00c59e0e
...
@@ -23,7 +23,8 @@ describe('PortProvider', () => {
...
@@ -23,7 +23,8 @@ describe('PortProvider', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
sinon
.
stub
(
window
,
'postMessage'
);
sinon
.
stub
(
window
,
'postMessage'
);
portProvider
=
new
PortProvider
(
window
.
location
.
href
);
portProvider
=
new
PortProvider
(
window
.
location
.
origin
);
portProvider
.
listen
();
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -31,6 +32,23 @@ describe('PortProvider', () => {
...
@@ -31,6 +32,23 @@ describe('PortProvider', () => {
portProvider
.
destroy
();
portProvider
.
destroy
();
});
});
describe
(
'#destroy'
,
()
=>
{
it
(
'ignores valid port request if `PortFinder` has been destroyed'
,
async
()
=>
{
portProvider
.
destroy
();
sendMessage
({
data
:
{
channel
:
'host-sidebar'
,
port
:
'sidebar'
,
source
,
type
:
'request'
,
},
});
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
});
});
describe
(
'#getPort'
,
()
=>
{
describe
(
'#getPort'
,
()
=>
{
it
(
'returns `null` if called with wrong arguments'
,
()
=>
{
it
(
'returns `null` if called with wrong arguments'
,
()
=>
{
let
hostPort
;
let
hostPort
;
...
@@ -59,20 +77,30 @@ describe('PortProvider', () => {
...
@@ -59,20 +77,30 @@ describe('PortProvider', () => {
});
});
});
});
describe
(
'#
destroy
'
,
()
=>
{
describe
(
'#
listen
'
,
()
=>
{
it
(
'ignores
valid port request if `PortFinder` has been destroyed
'
,
async
()
=>
{
it
(
'ignores
all port request until start listening
'
,
async
()
=>
{
portProvider
.
destroy
();
portProvider
.
destroy
();
portProvider
=
new
PortProvider
(
window
.
location
.
origin
);
const
data
=
{
channel
:
'host-sidebar'
,
port
:
'sidebar'
,
source
,
type
:
'request'
,
};
sendMessage
({
sendMessage
({
data
:
{
data
,
channel
:
'host-sidebar'
,
port
:
'sidebar'
,
source
,
type
:
'request'
,
},
});
});
await
delay
(
0
);
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
assert
.
notCalled
(
window
.
postMessage
);
portProvider
.
listen
();
sendMessage
({
data
,
});
await
delay
(
0
);
assert
.
calledOnce
(
window
.
postMessage
);
});
});
});
});
...
@@ -94,7 +122,6 @@ describe('PortProvider', () => {
...
@@ -94,7 +122,6 @@ describe('PortProvider', () => {
data
,
data
,
source
:
new
MessageChannel
().
port1
,
source
:
new
MessageChannel
().
port1
,
});
});
await
delay
(
0
);
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
assert
.
notCalled
(
window
.
postMessage
);
...
@@ -132,7 +159,6 @@ describe('PortProvider', () => {
...
@@ -132,7 +159,6 @@ describe('PortProvider', () => {
data
,
data
,
origin
:
'https://dummy.com'
,
origin
:
'https://dummy.com'
,
});
});
await
delay
(
0
);
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
assert
.
notCalled
(
window
.
postMessage
);
...
@@ -148,7 +174,6 @@ describe('PortProvider', () => {
...
@@ -148,7 +174,6 @@ describe('PortProvider', () => {
sendMessage
({
sendMessage
({
data
,
data
,
});
});
await
delay
(
0
);
await
delay
(
0
);
assert
.
calledWith
(
assert
.
calledWith
(
...
@@ -217,7 +242,7 @@ describe('PortProvider', () => {
...
@@ -217,7 +242,7 @@ describe('PortProvider', () => {
it
(
'sends the reciprocal port of the `guest-host` channel (via listener)'
,
async
()
=>
{
it
(
'sends the reciprocal port of the `guest-host` channel (via listener)'
,
async
()
=>
{
const
handler
=
sinon
.
stub
();
const
handler
=
sinon
.
stub
();
portProvider
.
addEventListener
(
'onH
ostPortRequest'
,
handler
);
portProvider
.
on
(
'h
ostPortRequest'
,
handler
);
const
data
=
{
const
data
=
{
channel
:
'guest-host'
,
channel
:
'guest-host'
,
port
:
'guest'
,
port
:
'guest'
,
...
...
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