Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- export type SocketOpenFunction = (event: Event) => void
- export type SocketMessageFunction = (event: MessageEvent) => void
- export type SocketErrorFunction = (event: RTCErrorEvent) => void
- export type SocketCloseFunction = (event: Event) => void
- const into = (func: Function) => func()
- export class SocketNotOpenError extends Error {
- constructor() {
- super(`Cannot send to a socket that's not 'open'. Wait for 'open' event.`)
- }
- }
- export class SocketTimeoutError extends Error {
- constructor() {
- super('Socket failed to connect due to timeout.')
- }
- }
- export class HandshakeError extends Error {
- constructor() {
- super('The remote host rejected this socket.')
- }
- }
- export class Socket extends Events {
- private channel!: RTCDataChannel
- public local!: string
- public remote!: string
- // note: we may want to consider buffering messages when there
- // are no subscribers, of perhaps subscribe on the underlying
- // channel on first call to either once or on.
- public once(event: 'open', func: SocketOpenFunction): void
- public once(event: 'message', func: SocketMessageFunction): void
- public once(event: 'error', func: SocketErrorFunction): void
- public once(event: 'close', func: SocketCloseFunction): void
- public once(event: string, func: EventHandler) {
- super.once(event, func)
- }
- public on(event: 'open', func: SocketOpenFunction): void
- public on(event: 'message', func: SocketMessageFunction): void
- public on(event: 'error', func: SocketErrorFunction): void
- public on(event: 'close', func: SocketCloseFunction): void
- public on(event: string, func: EventHandler) {
- super.on(event, func)
- }
- /** Sends a message to this socket. */
- public send(message: string | Blob | ArrayBuffer | ArrayBufferView) {
- if(!this.channel) {
- throw new SocketNotOpenError()
- }
- this.channel!.send(message as any)
- }
- /** Closes this socket. */
- public close() {
- this.channel.close()
- }
- /** Creates a socket from a datachannel sent from the smoke driver. */
- public static fromChannel(channel: RTCDataChannel, local: string, remote: string): Socket {
- const socket = new Socket()
- socket.remote = remote
- socket.local = local
- socket.channel = channel
- socket.channel.addEventListener('open', event => socket.emit('open', event))
- socket.channel.addEventListener('message', event => socket.emit('message', event))
- socket.channel.addEventListener('error', event => socket.emit('error', event))
- socket.channel.addEventListener('close', event => socket.emit('close', event))
- return socket
- }
- /**
- * Creates an outbound socket to the remote endpoint. This function will return a socket
- * immediately to the caller, but will asynchronously connect and handshake with the server.
- * The caller is expected to listen on this sockets events prior to interacting with
- * the socket.
- */
- public static createSocket(driver: Driver, remote: string, port: string): Socket {
- const socket = new Socket()
- into(async () => {
- // Resolve local and remote endpoints for this socket and rewrite local
- // and remote endpoints to 'loopback' if on localhost. The rewrite to loopback
- // is required for locating the appropriate localhost peer 'loopback:1'
- const local = await driver.address()
- socket.remote = (remote === 'localhost' || remote === local) ? 'loopback:1' : remote
- socket.local = local
- const peer = await driver.getPeer(socket.remote)
- socket.channel = peer.connection.createDataChannel(port)
- // We need to handle timeouts to the remote endpoint. Timeouts may occur
- // due to the remote host being unavailable, or due to network problems.
- // We set a reasonable connection timeout of 8 seconds.
- let waiting = true
- setTimeout(() => {
- if(waiting) {
- waiting = false
- socket.emit('error', new SocketTimeoutError())
- socket.emit('close')
- socket.channel.close()
- }
- }, 8000)
- // Wait for the channel to open then wait on the first message over
- // the channel to be the handshake 'sync' message from the server.
- // A server that fails to respond with a 'sync' is interpretted as
- // a rejection from that server. On sync, we respond to the server
- // with a 'sync' indicating that the client is responding.
- socket.channel.addEventListener('open', event => {
- if(waiting) {
- waiting = false
- const onHandshake = (message: MessageEvent) => {
- socket.channel.removeEventListener('message', onHandshake)
- if(message.data !== 'sync') {
- socket.emit('error', new HandshakeError())
- socket.emit('close')
- socket.channel.close()
- return
- }
- socket.channel.send('sync')
- socket.emit('open', event)
- socket.channel.addEventListener('message', event => socket.emit('message', event))
- socket.channel.addEventListener('error', event => socket.emit('error', event))
- socket.channel.addEventListener('close', event => socket.emit('close', event))
- }
- socket.channel.addEventListener('message', onHandshake)
- }
- })
- })
- return socket
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement