import { FakeMessageChannel , FakeMessagePort } from '../test_utils/channels' ; import { MockWindow } from '../test_utils/frames' ; import { createMessageEvent , createUntypedMessageEvent } from '../test_utils/messages' ; import { JasmineTimeoutManager } from '../test_utils/timeout' ; import { InternalErrorCode , OpenYoloInternalError } from './errors' ; import { ackMessage , channelConnectMessage , channelReadyMessage , readyForConnectMessage } from './post_messages' ; import * as msg from './rpc_messages' ; import { SecureChannel } from './secure_channel' ; import { sha256 } from './utils' ; describe ( 'SecureChannel' , ( ) => { let clientWindow ; let providerWindow ; let originalMessageChannel = MessageChannel ; let timeoutManager = new JasmineTimeoutManager ( ) ; beforeEach ( ( ) => { clientWindow = new MockWindow ( ) ; providerWindow = new MockWindow ( clientWindow ) ; MessageChannel = FakeMessageChannel ; timeoutManager . install ( ) ; } ) ; afterEach ( ( ) => { jasmine . clock ( ) . uninstall ( ) ; MessageChannel = originalMessageChannel ; timeoutManager . uninstall ( ) ; } ) ; describe ( 'clientConnect' , ( ) => { it ( 'performs an initialization handshake' , async function ( done ) { let connectNonce = '123' ; let connectNonceHash = await sha256 ( connectNonce ) ; spyOn ( providerWindow , 'postMessage' ) . and . callThrough ( ) ; let connectPromise = SecureChannel . clientConnect ( clientWindow , providerWindow , connectNonce , connectNonceHash ) ; providerWindow . addEventListener ( 'message' , ( ev ) => { expect ( ev . data ) . toEqual ( channelConnectMessage ( connectNonce ) ) ; clientWindow . postMessage ( channelReadyMessage ( connectNonceHash ) , '*' ) ; } ) ; clientWindow . postMessage ( readyForConnectMessage ( connectNonceHash ) , '*' ) ; try { await connectPromise ; done ( ) ; } catch ( err ) { done . fail ( 'Promise should resolve' ) ; } } ) ; } ) ; describe ( 'after initialization' , ( ) => { let channel ; let port ; beforeEach ( ( ) => { let messageChannel = new FakeMessageChannel ( ) ; port = messageChannel . port1 ; spyOn ( port , 'start' ) . and . callThrough ( ) ; spyOn ( port , 'addEventListener' ) . and . callThrough ( ) ; spyOn ( port , 'removeEventListener' ) . and . callThrough ( ) ; spyOn ( port , 'postMessage' ) . and . callThrough ( ) ; spyOn ( port , 'close' ) . and . callThrough ( ) ; channel = new SecureChannel ( port ) ; } ) ; it ( 'sends messages' , ( ) => { let message = msg . showProviderMessage ( '1234' , { } ) ; channel . send ( message ) ; expect ( port . postMessage ) . toHaveBeenCalledWith ( message ) ; } ) ; it ( 'adds and removes listener' , ( ) => { let listener1 = jasmine . createSpy ( 'listener1' ) ; let listener2 = jasmine . createSpy ( 'listener2' ) ; let listenerKey1 = channel . listen ( msg . RpcMessageType . showProvider , listener1 ) ; let listenerKey2 = channel . listen ( msg . RpcMessageType . showProvider , listener2 ) ; port . dispatchEvent ( createMessageEvent ( msg . showProviderMessage ( '123' , { } ) ) ) ; expect ( listener1 ) . toHaveBeenCalled ( ) ; expect ( listener2 ) . toHaveBeenCalled ( ) ; listener1 . calls . reset ( ) ; listener2 . calls . reset ( ) ; expect ( channel . unlisten ( listenerKey1 ) ) . toBe ( listener1 ) ; port . dispatchEvent ( createMessageEvent ( msg . showProviderMessage ( '123' , { } ) ) ) ; expect ( listener1 ) . not . toHaveBeenCalled ( ) ; expect ( listener2 ) . toHaveBeenCalled ( ) ; expect ( channel . unlisten ( listenerKey1 ) ) . toBe ( null ) ; expect ( channel . unlisten ( listenerKey2 ) ) . toBe ( listener2 ) ; } ) ; describe ( 'acknowledgement' , ( ) => { it ( 'sends and waits for acknowledgement' , ( done ) => { let message = msg . showProviderMessage ( '1234' , { } ) ; channel . sendAndWaitAck ( message ) . then ( ( ) => { expect ( port . removeEventListener ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Function ) ) ; done ( ) ; } ) ; port . dispatchEvent ( createMessageEvent ( ackMessage ( '1234' ) ) ) ; } ) ; it ( 'sends and waits for acknowledgement until timeout' , ( done ) => { let message = msg . showProviderMessage ( '1234' , { } ) ; let expectReject = false ; channel . sendAndWaitAck ( message ) . then ( ( ) => { done . fail ( 'It should not resolve the promise!' ) ; } , ( err ) => { expect ( expectReject ) . toBeTruthy ( 'Failed before timeout' ) ; expect ( OpenYoloInternalError . errorIs ( err , InternalErrorCode . ackTimeout ) ) . toBeTruthy ( ) ; expect ( port . removeEventListener ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Function ) ) ; done ( ) ; } ) ; port . dispatchEvent ( createMessageEvent ( ackMessage ( '123' ) ) ) ; jasmine . clock ( ) . tick ( 499 ) ; expectReject = true ; jasmine . clock ( ) . tick ( 1 ) ; } ) ; it ( 'sends back ack when required' , ( ) => { const listener1 = jasmine . createSpy ( 'listener1' ) ; channel . listen ( msg . RpcMessageType . showProvider , listener1 ) ; const message = msg . showProviderMessage ( '123' , { } ) ; message . data . ack = true ; port . dispatchEvent ( createMessageEvent ( message ) ) ; const expectedMessage = ackMessage ( '123' ) ; expect ( port . postMessage ) . toHaveBeenCalledWith ( expectedMessage ) ; } ) ; } ) ; describe ( 'dispose' , ( ) => { it ( 'removes listeners and closes' , ( ) => { let listener1 = jasmine . createSpy ( 'listener1' ) ; let listener2 = jasmine . createSpy ( 'listener2' ) ; channel . listen ( msg . RpcMessageType . showProvider , listener1 ) ; channel . listen ( msg . RpcMessageType . showProvider , listener2 ) ; channel . dispose ( ) ; expect ( port . removeEventListener ) . toHaveBeenCalledTimes ( 1 ) ; expect ( port . close ) . toHaveBeenCalled ( ) ; } ) ; } ) ; } ) ; describe ( 'providerConnect' , ( ) => { let id = '123' ; let hashId = '' ; let origin = 'https://example.com' ; let permittedOrigins = [ origin ] ; let port ; beforeEach ( async function ( done ) { let channel = new FakeMessageChannel ( ) ; port = channel . port2 ; spyOn ( port , 'start' ) . and . callThrough ( ) ; spyOn ( port , 'postMessage' ) . and . callThrough ( ) ; spyOn ( port , 'close' ) . and . callThrough ( ) ; hashId = await sha256 ( id ) ; done ( ) ; } ) ; it ( 'succeeds after valid handshake' , async function ( done ) { let promise = SecureChannel . providerConnect ( providerWindow , permittedOrigins , hashId ) ; providerWindow . dispatchEvent ( createUntypedMessageEvent ( 'unknown' , origin ) ) ; providerWindow . postMessageFromOrigin ( channelConnectMessage ( 'other' ) , [ port ] , origin , clientWindow ) ; providerWindow . postMessageFromOrigin ( channelConnectMessage ( id ) , [ port ] , origin , clientWindow ) ; try { let channel = await promise ; expect ( channel ) . toBeDefined ( ) ; done ( ) ; } catch ( err ) { done . fail ( 'Promise should resolve' ) ; } } ) ; it ( 'rejects if invalid origin' , async function ( done ) { let evilOrigin = 'https://evil.example.com' ; let connectPromise = SecureChannel . providerConnect ( providerWindow , permittedOrigins , hashId ) ; providerWindow . postMessageFromOrigin ( channelConnectMessage ( id ) , [ port ] , evilOrigin , clientWindow ) ; try { await connectPromise ; done . fail ( 'Promise should reject' ) ; } catch ( err ) { expect ( err ) . toEqual ( OpenYoloInternalError . untrustedOrigin ( evilOrigin ) ) ; done ( ) ; } } ) ; } ) ; } ) ;