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
Show 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';
import
{
isMessageEqual
,
SOURCE
as
source
}
from
'./port-util'
;
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
...
...
@@ -12,8 +12,7 @@ const POLLING_INTERVAL_FOR_PORT = 500;
*/
/**
* PortFinder class should be used in frames that are not the `host` frame. It
* helps to discover `MessagePort` on a specific channel.
* PortFinder helps to discover `MessagePort` on a specific channel.
*
* Channel nomenclature is `[frame1]-[frame2]` so that:
* - `port1` should be owned by/transferred to `frame1`, and
...
...
@@ -28,71 +27,42 @@ export class PortFinder {
this
.
_listeners
=
new
ListenerCollection
();
}
// Two important characteristics of `MessagePort`:
// - it can only be used by one frame; the port is neutered if, after started to
// 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()`)
destroy
()
{
this
.
_listeners
.
removeAll
();
}
/**
* `guest-host` communication
* @typedef {{channel: 'guest-host', hostFrame: Window, port: 'guest'}} options0
*
* `guest-sidebar` communication
* @typedef {{channel: 'guest-sidebar', hostFrame: Window, port: 'guest'}} options1
*
* `host-sidebar` communication
* @typedef {{channel: 'host-sidebar', hostFrame: Window, port: 'sidebar'}} options2
* Polls the hostFrame for a specific port and returns a Promise of the port.
*
* `notebook-sidebar` communication
* @typedef {{channel: 'notebook-sidebar', hostFrame: Window, port: 'notebook'}} options3
*
* @param {options0|options1|options2|options3} options
* @param {object} options
* @param {Channel} options.channel - requested channel
* @param {Window} options.hostFrame - frame where the hypothesis client is
* loaded and `PortProvider` is listening for messages
* @param {Port} options.port - requested port
* @return {Promise<MessagePort>}
*/
discover
(
options
)
{
const
{
channel
,
port
}
=
options
;
return
new
Promise
((
resolve
,
reject
)
=>
{
discover
({
channel
,
hostFrame
,
port
})
{
let
isValidRequest
=
false
;
if
(
(
channel
===
'guest-host'
&&
port
===
'guest'
)
||
(
channel
===
'guest-sidebar'
&&
port
===
'guest'
)
||
(
channel
===
'host-sidebar'
&&
port
===
'sidebar'
)
||
(
channel
===
'notebook-sidebar'
&&
port
===
'notebook'
)
)
{
this
.
_requestPort
({
...
options
,
reject
,
resolve
,
});
return
;
isValidRequest
=
true
;
}
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
isValidRequest
)
{
reject
(
new
Error
(
'Invalid request of channel/port'
));
})
;
return
;
}
/**
* @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
(
const
intervalId
=
setInterval
(
()
=>
postRequest
(),
POLLING_INTERVAL_FOR_PORT
);
...
...
@@ -106,40 +76,16 @@ export class PortFinder {
},
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
();
}
/**
* Resolve with a MessagePort when the `offer` message matches.
*
* @param {MessageEvent} event
* @param {object} options
* @param {Message} options.message
* @param {(port: MessagePort) => void} options.resolve
* @param {number} options.timeoutId
* @param {number} [options.intervalId]
*/
_handlePortOffer
(
{
data
,
ports
},
{
message
,
resolve
,
timeoutId
,
intervalId
}
)
{
if
(
isMessageEqual
(
data
,
message
))
{
this
.
_listeners
.
add
(
window
,
'message'
,
event
=>
{
const
{
data
,
ports
}
=
/** @type {MessageEvent} */
(
event
);
if
(
isMessageEqual
(
data
,
{
channel
,
port
,
source
,
type
:
'offer'
}))
{
clearInterval
(
intervalId
);
clearTimeout
(
timeoutId
);
resolve
(
ports
[
0
]);
}
}
});
destroy
()
{
this
.
_listeners
.
removeAll
(
);
postRequest
();
}
);
}
}
src/shared/port-provider.js
View file @
00c59e0e
import
{
TinyEmitter
}
from
'tiny-emitter'
;
import
{
ListenerCollection
}
from
'./listener-collection'
;
import
{
isMessageEqual
,
SOURCE
as
source
}
from
'./port-util'
;
...
...
@@ -38,7 +40,7 @@ import { isMessageEqual, SOURCE as source } from './port-util';
* - `host-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`
* for two frames to communicate with each other. It also listens to requests for
* particular `MessagePort` and dispatches the corresponding `MessagePort`.
...
...
@@ -55,7 +57,14 @@ import { isMessageEqual, SOURCE as source } from './port-util';
* V
* 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`
* 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
...
...
@@ -63,23 +72,27 @@ import { isMessageEqual, SOURCE as source } from './port-util';
* Therefore, for the `PortFinder`, we implement a polling strategy (sending a
* message every N milliseconds) until a response is received.
*
* 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
* Two important characteristics of `MessagePort`:
* - it can only be used by one frame: in Chrome the port is neutered if transferred twice
* - messages are queued until the other port is ready to listen (`port.start()`)
*/
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
)
{
this
.
_hypothesisAppsOrigin
=
new
URL
(
hypothesisAppsURL
).
origin
;
constructor
(
hypothesisAppsOrigin
)
{
this
.
_hypothesisAppsOrigin
=
hypothesisAppsOrigin
;
this
.
_emitter
=
new
TinyEmitter
();
// Although some channels (v.gr. `notebook-sidebar`) have only one
// `MessageChannel`, other channels (v.gr. `guest-sidebar`) can have multiple
// `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>>} */
this
.
_channels
=
new
Map
();
...
...
@@ -88,16 +101,74 @@ export class PortProvider {
this
.
_hostSidebarChannel
=
new
MessageChannel
();
this
.
_listeners
=
new
ListenerCollection
();
this
.
_listen
();
}
/**
* @param {'onHostPortRequest'} _eventName
* Checks the `postMessage` origin and message.
*
* @param {MessageEvent} event
* @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
(
event
,
allowedMessage
,
allowedOrigin
)
{
const
{
data
,
origin
,
source
}
=
event
;
if
(
allowedOrigin
!==
'*'
&&
origin
!==
allowedOrigin
)
{
return
false
;
}
if
(
// `source` can be of type Window | MessagePort | ServiceWorker.
// The simple check `source instanceof Window`` doesn't work here.
// Alternatively, `source` could be casted `/** @type{Window} */ (source)`
!
source
||
source
instanceof
MessagePort
||
source
instanceof
ServiceWorker
)
{
return
false
;
}
if
(
!
isMessageEqual
(
data
,
allowedMessage
))
{
return
false
;
}
return
true
;
}
/**
* Send a message and a port to the corresponding destinations.
*
* @param {MessageEvent} event
* @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
,
message
,
port
,
reciprocalPort
)
{
const
source
=
/** @type {Window} */
(
event
.
source
);
source
.
postMessage
(
message
,
event
.
origin
,
[
port
]);
if
(
reciprocalPort
)
{
if
([
'notebook-sidebar'
,
'guest-sidebar'
].
includes
(
message
.
channel
))
{
this
.
_hostSidebarChannel
.
port1
.
postMessage
(
message
,
[
reciprocalPort
]);
}
if
(
message
.
channel
===
'guest-host'
&&
message
.
port
===
'guest'
)
{
this
.
_emitter
.
emit
(
'hostPortRequest'
,
reciprocalPort
,
message
.
port
);
}
}
}
/**
* @param {'hostPortRequest'} 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
;
on
(
eventName
,
handler
)
{
this
.
_
emitter
.
on
(
eventName
,
handler
)
;
}
/**
...
...
@@ -119,7 +190,9 @@ export class PortProvider {
/**
* Initiate the listener of port requests by other frames.
*/
_listen
()
{
listen
()
{
this
.
_listeners
.
add
(
window
,
'message'
,
messageEvent
=>
{
const
event
=
/** @type {MessageEvent} */
(
messageEvent
);
/** @type {Array<{allowedOrigin: string, channel: Channel, port: Port}>} */
([
{
...
...
@@ -143,102 +216,26 @@ export class PortProvider {
port
:
'notebook'
,
},
]).
forEach
(({
allowedOrigin
,
channel
,
port
})
=>
{
this
.
_listeners
.
add
(
window
,
'message'
,
event
=>
this
.
_handlePortRequest
(
/** @type {MessageEvent} */
(
event
),
{
allowedMessage
:
{
/** @type {Message} */
const
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.
*
* @param {MessageEvent} event
* @param {Options} options
*/
_isValidRequest
({
data
,
origin
,
source
},
{
allowedMessage
,
allowedOrigin
})
{
if
(
allowedOrigin
!==
'*'
&&
origin
!==
allowedOrigin
)
{
return
false
;
}
if
(
// `source` can be of type Window | MessagePort | ServiceWorker.
// The simple check `source instanceof Window`` doesn't work here.
// Alternatively, `source` could be casted `/** @type{Window} */ (source)`
!
source
||
source
instanceof
MessagePort
||
source
instanceof
ServiceWorker
)
{
return
false
;
}
if
(
!
isMessageEqual
(
data
,
allowedMessage
))
{
return
false
;
}
return
true
;
}
/**
* Send (1) the requested port via `frame#postMessage` (the origin is set
* 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 {Options & {port: MessagePort, reciprocalPort? : MessagePort}} options
*/
_sendPort
(
event
,
{
allowedMessage
,
port
,
reciprocalPort
})
{
const
message
=
{
...
allowedMessage
,
type
:
'offer'
};
const
source
=
/** @type {Window} */
(
event
.
source
);
source
.
postMessage
(
message
,
event
.
origin
,
[
port
]);
if
(
reciprocalPort
)
{
if
([
'notebook-sidebar'
,
'guest-sidebar'
].
includes
(
message
.
channel
))
{
this
.
_hostSidebarChannel
.
port1
.
postMessage
(
message
,
[
reciprocalPort
]);
}
if
(
message
.
channel
===
'guest-host'
&&
message
.
port
===
'guest'
)
{
this
.
_onHostPortRequest
?.(
reciprocalPort
,
message
.
port
);
}
}
}
/**
* Respond to request of ports on channels.
* @param {MessageEvent} event
* @param {Options} options
*/
_handlePortRequest
(
event
,
options
)
{
if
(
!
this
.
_isValidRequest
(
event
,
options
))
{
if
(
!
this
.
_isValidRequest
(
event
,
allowedMessage
,
allowedOrigin
))
{
return
;
}
const
{
channel
}
=
options
.
allowedMessage
;
let
windowChannelMap
=
this
.
_channels
.
get
(
channel
);
if
(
!
windowChannelMap
)
{
windowChannelMap
=
new
Map
();
this
.
_channels
.
set
(
channel
,
windowChannelMap
);
}
const
s
ource
=
/** @type {Window} */
(
event
.
source
);
let
messageChannel
=
windowChannelMap
.
get
(
s
ource
);
const
eventS
ource
=
/** @type {Window} */
(
event
.
source
);
let
messageChannel
=
windowChannelMap
.
get
(
eventS
ource
);
// Ignore the port request if the channel for the specified window has
// already been created. This is to avoid transfer the port more than once.
...
...
@@ -246,22 +243,24 @@ export class PortProvider {
return
;
}
/** @type {Message} */
const
message
=
{
...
allowedMessage
,
type
:
'offer'
};
// `host-sidebar` channel is an special case, because it is created in the
// constructor.
if
(
channel
===
'host-sidebar'
)
{
windowChannelMap
.
set
(
source
,
this
.
_hostSidebarChannel
);
this
.
_sendPort
(
event
,
{
...
options
,
port
:
this
.
_hostSidebarChannel
.
port2
,
});
windowChannelMap
.
set
(
eventSource
,
this
.
_hostSidebarChannel
);
this
.
_sendPort
(
event
,
message
,
this
.
_hostSidebarChannel
.
port2
);
return
;
}
messageChannel
=
new
MessageChannel
();
windowChannelMap
.
set
(
s
ource
,
messageChannel
);
windowChannelMap
.
set
(
eventS
ource
,
messageChannel
);
const
{
port1
,
port2
}
=
messageChannel
;
this
.
_sendPort
(
event
,
{
...
options
,
port
:
port1
,
reciprocalPort
:
port2
});
this
.
_sendPort
(
event
,
message
,
port1
,
port2
);
});
});
}
destroy
()
{
...
...
src/shared/port-util.js
View file @
00c59e0e
// Because there are many `postMessages` on the `host` frame, the SOURCE property
// is added to the hypothesis `postMessages` to identify the provenance of the
// 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.
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'|'notebook'|'sidebar'} Port
*
...
...
src/shared/test/port-finder-test.js
View file @
00c59e0e
...
...
@@ -26,9 +26,46 @@ describe('PortFinder', () => {
portFinder
.
destroy
();
});
describe
(
'#destroy'
,
()
=>
{
it
(
'ignores `offer` messages of ports'
,
async
()
=>
{
let
error
;
const
channel
=
'host-sidebar'
;
const
port
=
'sidebar'
;
const
{
port1
}
=
new
MessageChannel
();
const
clock
=
sinon
.
useFakeTimers
();
try
{
portFinder
.
discover
({
channel
,
hostFrame
:
window
,
port
,
})
.
catch
(
e
=>
(
error
=
e
));
portFinder
.
destroy
();
sendMessage
({
data
:
{
channel
,
port
,
source
,
type
:
'offer'
},
ports
:
[
port1
],
});
clock
.
tick
(
30000
);
}
finally
{
clock
.
restore
();
}
await
delay
(
0
);
assert
.
equal
(
error
.
message
,
"Unable to find 'sidebar' port on 'host-sidebar' channel"
);
});
});
describe
(
'#discover'
,
()
=>
{
[
{
channel
:
'invalid'
,
port
:
'guest'
},
{
channel
:
'guest-host'
,
port
:
'invalid'
},
{
channel
:
'guest-host'
,
port
:
'host'
},
].
forEach
(({
channel
,
port
})
=>
it
(
'rejects if requesting an invalid port'
,
async
()
=>
{
let
error
;
...
...
@@ -91,7 +128,7 @@ describe('PortFinder', () => {
clock
.
restore
();
}
assert
.
callCount
(
window
.
postMessage
,
6
1
);
assert
.
callCount
(
window
.
postMessage
,
12
1
);
assert
.
alwaysCalledWithExactly
(
window
.
postMessage
,
{
channel
,
port
,
source
,
type
:
'request'
},
...
...
@@ -105,39 +142,5 @@ describe('PortFinder', () => {
"Unable to find 'sidebar' port on 'host-sidebar' channel"
);
});
describe
(
'#destroy'
,
()
=>
{
it
(
'ignores `offer` messages of ports'
,
async
()
=>
{
let
error
;
const
channel
=
'host-sidebar'
;
const
port
=
'sidebar'
;
const
{
port1
}
=
new
MessageChannel
();
const
clock
=
sinon
.
useFakeTimers
();
try
{
portFinder
.
discover
({
channel
,
hostFrame
:
window
,
port
,
})
.
catch
(
e
=>
(
error
=
e
));
portFinder
.
destroy
();
sendMessage
({
data
:
{
channel
,
port
,
source
,
type
:
'offer'
},
ports
:
[
port1
],
});
clock
.
tick
(
30000
);
}
finally
{
clock
.
restore
();
}
await
delay
(
0
);
assert
.
equal
(
error
.
message
,
"Unable to find 'sidebar' port on 'host-sidebar' channel"
);
});
});
});
src/shared/test/port-provider-test.js
View file @
00c59e0e
...
...
@@ -23,7 +23,8 @@ describe('PortProvider', () => {
beforeEach
(()
=>
{
sinon
.
stub
(
window
,
'postMessage'
);
portProvider
=
new
PortProvider
(
window
.
location
.
href
);
portProvider
=
new
PortProvider
(
window
.
location
.
origin
);
portProvider
.
listen
();
});
afterEach
(()
=>
{
...
...
@@ -31,6 +32,23 @@ describe('PortProvider', () => {
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'
,
()
=>
{
it
(
'returns `null` if called with wrong arguments'
,
()
=>
{
let
hostPort
;
...
...
@@ -59,20 +77,30 @@ describe('PortProvider', () => {
});
});
describe
(
'#
destroy
'
,
()
=>
{
it
(
'ignores
valid port request if `PortFinder` has been destroyed
'
,
async
()
=>
{
describe
(
'#
listen
'
,
()
=>
{
it
(
'ignores
all port request until start listening
'
,
async
()
=>
{
portProvider
.
destroy
();
sendMessage
({
data
:
{
portProvider
=
new
PortProvider
(
window
.
location
.
origin
);
const
data
=
{
channel
:
'host-sidebar'
,
port
:
'sidebar'
,
source
,
type
:
'request'
,
},
};
sendMessage
({
data
,
});
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
portProvider
.
listen
();
sendMessage
({
data
,
});
await
delay
(
0
);
assert
.
calledOnce
(
window
.
postMessage
);
});
});
...
...
@@ -94,7 +122,6 @@ describe('PortProvider', () => {
data
,
source
:
new
MessageChannel
().
port1
,
});
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
...
...
@@ -132,7 +159,6 @@ describe('PortProvider', () => {
data
,
origin
:
'https://dummy.com'
,
});
await
delay
(
0
);
assert
.
notCalled
(
window
.
postMessage
);
...
...
@@ -148,7 +174,6 @@ describe('PortProvider', () => {
sendMessage
({
data
,
});
await
delay
(
0
);
assert
.
calledWith
(
...
...
@@ -217,7 +242,7 @@ describe('PortProvider', () => {
it
(
'sends the reciprocal port of the `guest-host` channel (via listener)'
,
async
()
=>
{
const
handler
=
sinon
.
stub
();
portProvider
.
addEventListener
(
'onH
ostPortRequest'
,
handler
);
portProvider
.
on
(
'h
ostPortRequest'
,
handler
);
const
data
=
{
channel
:
'guest-host'
,
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