(function (context, $) {
  "use strict";

  context.JK = context.JK || {};

  class Deferred {
    constructor(request_id) {
      var self = this;
      this.request_id = request_id;
      this.promise = new Promise(function (resolve, reject) {
        self.reject = reject;
        self.resolve = resolve;
      });
    }
  }

  context.JK.AsyncJamClient = function (app) {
    const self = this;
    const logger = context.JK.logger;
    let deferredQueue = [];
    let jkfrontendchannel = null;
    let enumAppCounter = 1000;
    let request_id = 1;

    const JKFrontendMethods = Object.freeze({
      UnknownJKAppMessage: enumAppCounter++,
      AbortRecording: enumAppCounter++,
      addUserBackingTracksToJamkazamAsset: enumAppCounter++,
      applySubscriptionPolicy: enumAppCounter++,
      BringVideoWindowToFront: enumAppCounter++,
      browseForUserBackingTracksCandidate: enumAppCounter++,
      cancelVideoUpload: enumAppCounter++,
      clientID: enumAppCounter++,
      ClientJoinedSession: enumAppCounter++,
      ClientLeftSession: enumAppCounter++,
      ClientUpdateStartDownload: enumAppCounter++,
      ClientUpdateStartUpdate: enumAppCounter++,
      ClientUpdateVersion: enumAppCounter++,
      ClosePreviewRecording: enumAppCounter++,
      CloseRecording: enumAppCounter++,
      deleteVideo: enumAppCounter++,
      DownloadFileStatus: enumAppCounter++,
      endPairing: enumAppCounter++,
      FTUEAudioResync: enumAppCounter++,
      FTUECancel: enumAppCounter++,
      FTUEChatFrameSize: enumAppCounter++,
      FTUEClearChannelAssignments: enumAppCounter++,
      FTUEClearChatInput: enumAppCounter++,
      FTUECreateUpdatePlayBackProfile: enumAppCounter++,
      FTUECurrentSelectedVideoDevice: enumAppCounter++,
      FTUEGetAllAudioConfigurations: enumAppCounter++,
      FTUEGetAllConfigurationList: enumAppCounter++,
      FTUEGetAudioConfigurationsMissingDevice: enumAppCounter++,
      FTUEGetAudioDevices: enumAppCounter++,
      FTUEGetAutoSelectVideoLayout: enumAppCounter++,
      FTUEGetAvailableEncodeVideoResolutions: enumAppCounter++,
      FTUEGetAvailableVideoLayoutGroupStyles: enumAppCounter++,
      FTUEGetBadConfigurationMissingDev: enumAppCounter++,
      FTUEGetCaptureResolution: enumAppCounter++,
      FTUEGetChannels: enumAppCounter++,
      FTUEGetChatInputs: enumAppCounter++,
      FTUEGetChatInputVolume: enumAppCounter++,
      FTUEGetChatLatency: enumAppCounter++,
      FTUEGetConfigurationDevice: enumAppCounter++,
      FTUEGetConfigurationDevs: enumAppCounter++,
      FTUEGetCurrentCaptureResolution: enumAppCounter++,
      FTUEGetCurrentVideoCaptureDeviceCapability: enumAppCounter++,
      FTUEGetDefaultAudioConfigurations: enumAppCounter++,
      FTUEGetDevices: enumAppCounter++,
      FTUEGetExpectedLatency: enumAppCounter++,
      FTUEGetFrameSize: enumAppCounter++,
      FTUEGetGoodAudioConfigurations: enumAppCounter++,
      FTUEGetGoodConfigurationList: enumAppCounter++,
      FTUEgetInputDeviceSampleRate: enumAppCounter++,
      FTUEGetInputLatency: enumAppCounter++,
      FTUEGetInputMusicDevice: enumAppCounter++,
      FTUEGetInputVolume: enumAppCounter++,
      FTUEGetIoPerfData: enumAppCounter++,
      FTUEGetMapUserCntPreferredVideoLayoutGroupStyle: enumAppCounter++,
      FTUEGetMusicInputs: enumAppCounter++,
      FTUEGetMusicOutputs: enumAppCounter++,
      FTUEGetMusicProfileName: enumAppCounter++,
      FTUEgetOutputDeviceSampleRate: enumAppCounter++,
      FTUEGetOutputLatency: enumAppCounter++,
      FTUEGetOutputMusicDevice: enumAppCounter++,
      FTUEGetOutputVolume: enumAppCounter++,
      FTUEGetPreferredChatSampleRate: enumAppCounter++,
      FTUEGetPreferredMixerSampleRate: enumAppCounter++,
      FTUEGetPreferredOutputSampleRate: enumAppCounter++,
      FTUEGetSendFrameRates: enumAppCounter++,
      FTUEGetStatus: enumAppCounter++,
      FTUEGetUserCountPreferredVideoLayoutGroupStyle: enumAppCounter++,
      FTUEGetVideoCaptureDeviceCapabilities: enumAppCounter++,
      FTUEGetVideoCaptureDeviceNames: enumAppCounter++,
      FTUEGetVideoShareEnable: enumAppCounter++,
      FTUEGetVolumeRanges: enumAppCounter++,
      FTUEHasControlPanel: enumAppCounter++,
      FTUEInit: enumAppCounter++,
      FTUEIsMusicDeviceWDM: enumAppCounter++,
      FTUELoadAudioConfiguration: enumAppCounter++,
      FTUEOpenControlPanel: enumAppCounter++,
      FTUEPageEnter: enumAppCounter++,
      FTUEPageLeave: enumAppCounter++,
      FTUERefreshDevices: enumAppCounter++,
      FTUERegisterLatencyCallback: enumAppCounter++,
      FTUERegisterVUCallbacks: enumAppCounter++,
      FTUESave: enumAppCounter++,
      FTUESelectVideoCaptureDevice: enumAppCounter++,
      FTUESetAutoSelectVideoLayout: enumAppCounter++,
      FTUESetCaptureResolution: enumAppCounter++,
      FTUESetChatInput: enumAppCounter++,
      FTUESetChatInputVolume: enumAppCounter++,
      FTUESetChatLatency: enumAppCounter++,
      FTUESetFrameSize: enumAppCounter++,
      FTUESetInputLatency: enumAppCounter++,
      FTUESetInputMusicDevice: enumAppCounter++,
      FTUESetInputVolume: enumAppCounter++,
      FTUESetLatencySamples: enumAppCounter++,
      FTUESetMusicDevice: enumAppCounter++,
      FTUESetMusicInput: enumAppCounter++,
      FTUESetMusicInput2: enumAppCounter++,
      FTUESetMusicOutput: enumAppCounter++,
      FTUESetMusicProfileName: enumAppCounter++,
      FTUESetOutputLatency: enumAppCounter++,
      FTUESetOutputMusicDevice: enumAppCounter++,
      FTUESetOutputVolume: enumAppCounter++,
      FTUESetPreferredChatSampleRate: enumAppCounter++,
      FTUESetPreferredMixerSampleRate: enumAppCounter++,
      FTUESetPreferredOutputSampleRate: enumAppCounter++,
      FTUESetPreferredVideoLayoutGroupStyle: enumAppCounter++,
      FTUESetSendFrameRates: enumAppCounter++,
      FTUESetStatus: enumAppCounter++,
      FTUESetUserCountPreferredVideoLayoutGroupStyle: enumAppCounter++,
      FTUESetVideoEncodeResolution: enumAppCounter++,
      FTUESetVideoShareEnable: enumAppCounter++,
      FTUEStartIoPerfTest: enumAppCounter++,
      FTUEStartLatency: enumAppCounter++,
      FTUEUnsetMusicInput2: enumAppCounter++,
      get48vAndLineInstState: enumAppCounter++,
      getABLoopState: enumAppCounter++,
      getAllClientsStateMap: enumAppCounter++,
      getAllClientsStateStr: enumAppCounter++,
      GetASIODevices: enumAppCounter++,
      GetAutoStart: enumAppCounter++,
      getAvailableMetronomeClickNames: enumAppCounter++,
      getBackingTrackList: enumAppCounter++,
      getClientParentChildRole: enumAppCounter++,
      getConnectionDetail: enumAppCounter++,
      GetCurrentVideoFrameRate: enumAppCounter++,
      GetCurrentVideoResolution: enumAppCounter++,
      GetDetailedOS: enumAppCounter++,
      GetFTUE: enumAppCounter++,
      GetJamTrackSettings: enumAppCounter++,
      GetJamTrackTimeline: enumAppCounter++,
      getJBAutoPair: enumAppCounter++,
      getJbNetworkState: enumAppCounter++,
      getJbPortBindState: enumAppCounter++,
      getJbPreampState: enumAppCounter++,
      getJbStaticPortFlag: enumAppCounter++,
      getJbStaticPortValue: enumAppCounter++,
      getJbTrackState: enumAppCounter++,
      getJbUsbState: enumAppCounter++,
      GetLastLatencyTestTimes: enumAppCounter++,
      getLatencyServerState: enumAppCounter++,
      getLocalClients: enumAppCounter++,
      GetLocalRecordingState: enumAppCounter++,
      getLogLevel: enumAppCounter++,
      getMetronomeCricketTestState: enumAppCounter++,
      GetMixerIDs: enumAppCounter++,
      GetMixerMode: enumAppCounter++,
      GetMixerVolume: enumAppCounter++,
      getMyChildren: enumAppCounter++,
      getMyNetworkState: enumAppCounter++,
      getMyParentState: enumAppCounter++,
      GetNetworkTestScore: enumAppCounter++,
      getOpenVideoSources: enumAppCounter++,
      getOperatingMode: enumAppCounter++,
      GetOS: enumAppCounter++,
      GetOSAsString: enumAppCounter++,
      getParentClientId: enumAppCounter++,
      getPeerState: enumAppCounter++,
      GetRecordingManagerState: enumAppCounter++,
      GetSampleRate: enumAppCounter++,
      GetScoreWorkTimingInterval: enumAppCounter++,
      getSessionSetCompressorState: enumAppCounter++,
      GetStaticPort: enumAppCounter++,
      GetUseStaticPort: enumAppCounter++,
      GetVideoNetworkTestScore: enumAppCounter++,
      hasBonjour: enumAppCounter++,
      hasVstAssignment: enumAppCounter++,
      hasVstHost: enumAppCounter++,
      hasWebrtc: enumAppCounter++,
      InitiateScoringSession: enumAppCounter++,
      InvalidateJamTrack: enumAppCounter++,
      IsAppInWritableVolume: enumAppCounter++,
      IsAudioStarted: enumAppCounter++,
      IsFrontendVisible: enumAppCounter++,
      isJamBlaster: enumAppCounter++,
      isLatencyTestBlocked: enumAppCounter++,
      IsMyNetworkWireless: enumAppCounter++,
      IsNativeClient: enumAppCounter++,
      IsSessionLiveBroadCastRunning: enumAppCounter++,
      isSessionTrackPaused: enumAppCounter++,
      isSessionTrackPlaying: enumAppCounter++,
      isSessVideoRecording: enumAppCounter++,
      isSessVideoShared: enumAppCounter++,
      isSessYouTubeVideoBroadcasting: enumAppCounter++,
      isVideoRecordingAllowed: enumAppCounter++,
      isVideoWindowOpen: enumAppCounter++,
      IsVstLoaded: enumAppCounter++,
      isWebCamOpen: enumAppCounter++,
      jamBlasterSerialNo: enumAppCounter++,
      JamTrackDownload: enumAppCounter++,
      JamTrackGetImage: enumAppCounter++,
      JamTrackGetTrackDetail: enumAppCounter++,
      JamTrackGetTracks: enumAppCounter++,
      JamTrackIsPlayable: enumAppCounter++,
      JamTrackIsPlaying: enumAppCounter++,
      JamTrackKeysRequest: enumAppCounter++,
      JamTrackLoadJmep: enumAppCounter++,
      JamTrackPlay: enumAppCounter++,
      JamTrackStopPlay: enumAppCounter++,
      JoinSession: enumAppCounter++,
      LastUsedProfileName: enumAppCounter++,
      LeaveSession: enumAppCounter++,
      LeaveSessionAndMinimize: enumAppCounter++,
      log: enumAppCounter++,
      NetworkTest: enumAppCounter++,
      NetworkTestResult: enumAppCounter++,
      OnDownloadAvailable: enumAppCounter++,
      OnLoggedIn: enumAppCounter++,
      OnLoggedInUserName: enumAppCounter++,
      OnLoggedOut: enumAppCounter++,
      OnTrySyncCommand: enumAppCounter++,
      openBackingTrackFile: enumAppCounter++,
      OpenBackingTracksDirectory: enumAppCounter++,
      OpenRecording: enumAppCounter++,
      OpenSystemBrowser: enumAppCounter++,
      openVideo: enumAppCounter++,
      P2PMessageReceived: enumAppCounter++,
      ParticipantJoined: enumAppCounter++,
      ParticipantLeft: enumAppCounter++,
      pauseVideoUpload: enumAppCounter++,
      PerformQuit: enumAppCounter++,
      PostTaskBarMessage: enumAppCounter++,
      PreviewRecording: enumAppCounter++,
      rebootJamBlaster: enumAppCounter++,
      RegisterGenericCallBack: enumAppCounter++,
      registerMasterClippingCallback: enumAppCounter++,
      RegisterMixerInterfaceModeChangeCallback: enumAppCounter++,
      RegisterMuteChangeCallBack: enumAppCounter++,
      RegisterQuitCallback: enumAppCounter++,
      RegisterRecordingCallbacks: enumAppCounter++,
      RegisterRecordingManagerCallbacks: enumAppCounter++,
      RegisterSessionJoinLeaveRequestCallBack: enumAppCounter++,
      RegisterVolChangeCallBack: enumAppCounter++,
      ReloadAudioSystem: enumAppCounter++,
      removeBackingTrackFiles: enumAppCounter++,
      ResetPageCounters: enumAppCounter++,
      RestartApplication: enumAppCounter++,
      restartParentClient: enumAppCounter++,
      resumeVideoUpload: enumAppCounter++,
      SaveSettings: enumAppCounter++,
      SaveToClipboard: enumAppCounter++,
      SendP2PMessage: enumAppCounter++,
      SessGetInsetPosition: enumAppCounter++,
      SessGetListOfPeersSharingVideo: enumAppCounter++,
      SessGetPeersVideoPosition: enumAppCounter++,
      SessGetVideoDisplayLayoutGroup: enumAppCounter++,
      SessGetVideoReceiveEnable: enumAppCounter++,
      SessGetVideoRecordings: enumAppCounter++,
      SessionAddPlayTrack: enumAppCounter++,
      SessionAddTrack: enumAppCounter++,
      SessionAudioResync: enumAppCounter++,
      SessionCloseBackingTrackFile: enumAppCounter++,
      SessionCloseMetronome: enumAppCounter++,
      SessionCurrentPlaybackMode: enumAppCounter++,
      SessionCurrrentJamTrackPlayPosMs: enumAppCounter++,
      SessionCurrrentPlayPosMs: enumAppCounter++,
      SessionFirstGet: enumAppCounter++,
      SessionFirstSet: enumAppCounter++,
      SessionGetAllControlState: enumAppCounter++,
      SessionGetControlState: enumAppCounter++,
      SessionGetDeviceLatency: enumAppCounter++,
      SessionGetIDs: enumAppCounter++,
      SessionGetJamTracksPlayDurationMs: enumAppCounter++,
      SessionGetMacHash: enumAppCounter++,
      SessionGetMasterLocalMix: enumAppCounter++,
      SessionGetTracksPlayDurationMs: enumAppCounter++,
      SessionJamTrackSeekMs: enumAppCounter++,
      SessionLiveBroadcastStart: enumAppCounter++,
      SessionLiveBroadcastStop: enumAppCounter++,
      SessionOpenBackingTrackFile: enumAppCounter++,
      SessionOpenMetronome: enumAppCounter++,
      SessionPageEnter: enumAppCounter++,
      SessionPageLeave: enumAppCounter++,
      SessionPausePlay: enumAppCounter++,
      SessionRegisterCallback: enumAppCounter++,
      SessionRemoveAllPlayTracks: enumAppCounter++,
      SessionRemovePlayTrack: enumAppCounter++,
      SessionRemoveTrack: enumAppCounter++,
      SessionRequestUserControlUpdate: enumAppCounter++,
      SessionSetAlertCallback: enumAppCounter++,
      SessionSetAlertPeriod: enumAppCounter++,
      SessionSetConnectionStatusRefreshRate: enumAppCounter++,
      SessionSetControlState: enumAppCounter++,
      SessionSetMasterLocalMix: enumAppCounter++,
      SessionSetMetronome: enumAppCounter++,
      SessionSetRecordingFilename: enumAppCounter++,
      SessionSetRecordingFolder: enumAppCounter++,
      SessionSetTrackVolumeData: enumAppCounter++,
      SessionSetUserData: enumAppCounter++,
      SessionSetUserName: enumAppCounter++,
      SessionShowMetronomeGui: enumAppCounter++,
      SessionStartPlay: enumAppCounter++,
      SessionStopPlay: enumAppCounter++,
      SessionTrackSeekMs: enumAppCounter++,
      SessionTracksSeek: enumAppCounter++,
      SessPlayVideoRecording: enumAppCounter++,
      SessRotatePeerVideoPositions: enumAppCounter++,
      SessSelectVideoDisplayLayoutGroup: enumAppCounter++,
      SessSetInsetPosition: enumAppCounter++,
      SessSetInsetSize: enumAppCounter++,
      SessSetPeersVideoPosition: enumAppCounter++,
      SessSetVideoReceiveEnable: enumAppCounter++,
      SessStartVideoRecordAndReturnFileName: enumAppCounter++,
      SessStartVideoSharing: enumAppCounter++,
      SessStartWebcamVideoRecordAndReturnFileName: enumAppCounter++,
      SessStartYouTubeVideoBroadcast: enumAppCounter++,
      SessStopVideoRecord: enumAppCounter++,
      SessStopVideoSharing: enumAppCounter++,
      SessStopYouTubeVideoBroadcast: enumAppCounter++,
      SessSubscribePeerVideo: enumAppCounter++,
      SessUnsubscribePeerVideo: enumAppCounter++,
      set48vAndLineInstState: enumAppCounter++,
      setABLoopState: enumAppCounter++,
      SetAutoStart: enumAppCounter++,
      setBackingTrackOpenCallback: enumAppCounter++,
      SetFakeRecordingImpl: enumAppCounter++,
      SetFTUE: enumAppCounter++,
      setJBAutoPair: enumAppCounter++,
      setJBName: enumAppCounter++,
      setJbNetworkState: enumAppCounter++,
      setJbPortBindState: enumAppCounter++,
      setJbPreampState: enumAppCounter++,
      setJbStaticPortFlag: enumAppCounter++,
      setJbStaticPortValue: enumAppCounter++,
      setJbTrackState: enumAppCounter++,
      setJbUsbState: enumAppCounter++,
      setJmepMetronomeVolume: enumAppCounter++,
      SetLastUsedProfileName: enumAppCounter++,
      SetLatencyTestBlocked: enumAppCounter++,
      setMetronomeCricketTestState: enumAppCounter++,
      setMetronomeOpenCallback: enumAppCounter++,
      SetMixerMode: enumAppCounter++,
      SetNetworkTestScore: enumAppCounter++,
      SetRecordingFilename: enumAppCounter++,
      SetRecordingFolder: enumAppCounter++,
      SetScoreWorkTimingInterval: enumAppCounter++,
      setSessionMixerCategoryPlayoutState: enumAppCounter++,
      setSessionSetCompressorState: enumAppCounter++,
      SetStaticPort: enumAppCounter++,
      SetUseStaticPort: enumAppCounter++,
      SetVideoNetworkTestScore: enumAppCounter++,
      SetVURefreshRate: enumAppCounter++,
      ShowSelectBackingTrackDialog: enumAppCounter++,
      ShowSelectVSTScanDialog: enumAppCounter++,
      ShutdownApplication: enumAppCounter++,
      StartLiveStreaming: enumAppCounter++,
      startPairing: enumAppCounter++,
      StartRecording: enumAppCounter++,
      StopAudio: enumAppCounter++,
      StopLiveStreaming: enumAppCounter++,
      StopNetworkTest: enumAppCounter++,
      StopRecording: enumAppCounter++,
      TestLatency: enumAppCounter++,
      TestNetworkPktBwRate: enumAppCounter++,
      testVideoRender: enumAppCounter++,
      TrackDeleteProfile: enumAppCounter++,
      TrackGetAssignment: enumAppCounter++,
      TrackGetChannels: enumAppCounter++,
      TrackGetChatEnable: enumAppCounter++,
      TrackGetChatUsesMusic: enumAppCounter++,
      TrackGetCount: enumAppCounter++,
      TrackGetDevices: enumAppCounter++,
      TrackGetInstrument: enumAppCounter++,
      TrackGetMusicDeviceID: enumAppCounter++,
      TrackGetMusicDeviceNames: enumAppCounter++,
      TrackHasControlPanel: enumAppCounter++,
      TrackIsMusicDeviceType: enumAppCounter++,
      TrackLoadAssignments: enumAppCounter++,
      TrackLoadAudioProfile: enumAppCounter++,
      TrackOpenControlPanel: enumAppCounter++,
      TrackRefreshDevices: enumAppCounter++,
      TrackSaveAssignments: enumAppCounter++,
      TrackSetAssignment: enumAppCounter++,
      TrackSetChatEnable: enumAppCounter++,
      TrackSetChatInput: enumAppCounter++,
      TrackSetChatUsesMusic: enumAppCounter++,
      TrackSetCount: enumAppCounter++,
      TrackSetInstrument: enumAppCounter++,
      TrackSetMusicDevice: enumAppCounter++,
      UpdateMixer: enumAppCounter++,
      UpdateSessionInfo: enumAppCounter++,
      uploadVideo: enumAppCounter++,
      UserAttention: enumAppCounter++,
      VideoDecision: enumAppCounter++,
      VSTAddSearchPath: enumAppCounter++,
      VSTClearAll: enumAppCounter++,
      VSTListSearchPaths: enumAppCounter++,
      VSTListTrackAssignments: enumAppCounter++,
      VSTListVsts: enumAppCounter++,
      VSTLoad: enumAppCounter++,
      VSTRemoveSearchPath: enumAppCounter++,
      VSTScan: enumAppCounter++,
      VSTSetTrackAssignment: enumAppCounter++,
      VSTShowHideGui: enumAppCounter++,
      VST_EnableMidiForTrack: enumAppCounter++,
      VST_GetMidiDeviceList: enumAppCounter++,
      VST_ScanForMidiDevices: enumAppCounter++,
      IsOBSAvailable: enumAppCounter++,
      StartMediaRecording: enumAppCounter++,
      LaunchBroadcastSettings: enumAppCounter++,
      SetRecordingOptionStartedByPeer: enumAppCounter++,
      GetRecordingOptionStartedByPeer: enumAppCounter++,
      SetAudioRecordingPreference: enumAppCounter++,
      GetAudioRecordingPreference: enumAppCounter++,
      PeerStopRecording: enumAppCounter++,
      FrontStopRecording: enumAppCounter++,
      GetCurrentRecordingId: enumAppCounter++,
    });

    function setupWebSocketConnection() {
      const baseUrl = "ws://localhost:3060";
      logger.log(
        "[asyncJamClient] connecting to WebSocket server at " + baseUrl + "."
      );

      const socket = new WebSocket(baseUrl);

      socket.onclose = function (event) {
        logger.error("[asyncJamClient] QWebChannel websocket closed");
        if (event.code != 1000) {
          // Error code 1000 means that the connection was closed normally.
          // Try to reconnect.

          if (!navigator.onLine) {
            //alert("You are offline. Please connect to the Internet and try again.");
          }
        }
      };

      socket.onerror = function (error) {
        logger.error("[asyncJamClient] QWebChannel websocket error: " + error);
      };

      socket.onopen = function () {
        logger.log(
          "[asyncJamClient] WebSocket connected, setting up QWebChannel."
        );
        const skipLogMethods = [
          'getConnectionDetail'
        ]
        const displayLogMethod = [

        ]
        new QWebChannel(socket, function (channel) {
          
          jkfrontendchannel = channel.objects.jkfrontendchannel;
          
          if (jkfrontendchannel) {
            window.isWebChannelReady = true;
            let deferred;
            try {
              jkfrontendchannel.sendText.connect(function (message) {
                // logger.log(
                //   "[asyncJamClient] Message received via QWebChannel: " +
                //     message
                // );
                //handle Method Not Implemented"
                if (
                  typeof resp === "object" &&
                  resp[0] === "Method Not Implemented"
                ) {
                  //throw new Error("Method Not Implemented");
                  deferred.resolve(null); //return null value
                } else {
                  let msg = JSON.parse(message);
                  
                  let req_id = msg.request_id; //frontend originated request
                  let response = msg.response;
                  let evt_id = msg.event_id; //backend originated async event
                  let methodName = msg.method_name;

                  deferred = deferredQueue.find((d) => {
                    return d.request_id === req_id;
                  });

                  if (deferred) {
                    if(skipLogMethods.length > 0 && skipLogMethods.includes(methodName)){
                    }else{
                      if(displayLogMethod.includes(methodName)){
                        logger.log(
                          "[asyncJamClient] Message received via QWebChannel: " +
                            msg
                        );
                      }
                    }
                    
                    deferred.resolve(response);
                    //remove this deferred object from queue
                    deferredQueue = deferredQueue.filter(
                      (d) => d.request_id !== deferred.request_id
                    );
                  } else if (evt_id) {
                    let method = Object.keys(response)[0]
                    logger.log("[asyncJamClient] event received:", evt_id.toString(), Object.keys(response)[0])

                    // if(evt_id.toString() === '3012'){
                    //   alert(evt_id.toString())
                    // }

                    switch (evt_id.toString()) {
                      case '3006': //execute_script
                        if(response['execute_script'].match('HandleBridgeCallback2')){
                          logger.log(`[asyncJamClient] 3006 execute_script: ${response['execute_script']}`);
                        }
                        try {
                          eval(response['execute_script']);
                        } catch (error) {
                          logger.log(`[asyncJamClient] error: execute_script: ${response['execute_script']}`);
                          logger.log(error);
                        }
                        break;
                      case '3007': //message
                        //logger.log(`[asyncJamClient] 3007 message: ${response['message']}`);
                        try {
                          const msg       = response['message'];
                          const clientId  = response['targetClientID'];
                          context.JK.JamServer.sendP2PMessage(clientId, msg)
                        } catch (error) {
                          logger.log(`[asyncJamClient] error: sendP2PMessage: ${response['message']}`);
                          logger.log(error);
                        }
                        break;
                      case '3010': //JKVideoSession
                        logger.log(`[asyncJamClient] 3010 JKVideoSession: ${response['JKVideoSession']['connect']}`);
                        const vidConnect = response['JKVideoSession']['connect'];
                        context.ExternalVideoActions.setVideoEnabled(vidConnect);
                        context.JK.videoIsOngoing = vidConnect;
                        break;
                      case '3011': //AudioFormatChangeEvent
                        logger.log(`[asyncJamClient] 3011 AudioFormatChangeEvent: ${response['AudioFormat']}`);
                        const audioFormat = response['AudioFormat']
                        context.RecordingActions.audioRecordingFormatChanged(`.${audioFormat}`)
                        break;
                      case '3012': //customUrl
                        const customUrl = response['CustomUrl']
                        //if localStorage is available and has customUrl key, remove it
                        if(localStorage){
                          if(localStorage.getItem('customUrl')){
                            localStorage.removeItem('customUrl')
                          }
                          //now set the customUrl which received from backend
                          if(customUrl){
                            const httpUrl = window.location.protocol + '//' + customUrl;
                            const customUrlHandler = new JK.CustomUrlHandler();
                           
                            if(customUrlHandler.isValidUrl(httpUrl)){
                              localStorage.setItem('customUrl', httpUrl);
                              if(context.JK.currentUserId){
                                window.location.href = httpUrl;
                              }
                            }else{
                             logger.log(`[asyncJamClient] invalid customUrl: ${httpUrl}`);
                            }
                          }
                        }
                       
                        break;
                      default:
                        break;
                    }
                  }
                }
              });
              //TODO: handle method does not exist
            } catch (e) {
              logger.log(
                "[asyncJamClient] Error when receving message via QWebChannel"
              );
              if (deferred) {
                deferred.reject(e.message);
                deferred = null;
              }
              Bugsnag.notify(e, function(event){
                event.severity = 'info'
                event.context = 'asyncJamClient'
                event.setUser(gon.user_id, gon.user_email, gon.user_name)
              });
            }
          }
        });
      };
    }

    function waitForOpenConnection(){
      return new Promise((resolve, reject) => {
          const maxNumberOfAttempts = 10
          const intervalTime = 200 //ms
  
          let currentAttempt = 0
          const interval = setInterval(() => {
              if (currentAttempt > maxNumberOfAttempts - 1) {
                  clearInterval(interval)
                  reject(new Error('Maximum number of attempts exceeded'))
              } else if (jkfrontendchannel) {
                  clearInterval(interval)
                  resolve()
              }
              currentAttempt++
          }, intervalTime)
      })
    }

    async function sendWhenReady(prop, args){
      if (jkfrontendchannel) {
        return sendMessage(prop, args)
      }else{
        try {
          await waitForOpenConnection()
          return sendMessage(prop, args)
        } catch (err) { console.error(err) }
      }
    }

    function sendMessage(prop, args){
      let appMessage        = new Object();
      appMessage.request_id = ++request_id;
      appMessage.arguments  = Array.from(args) || [];
      let method            = JKFrontendMethods[prop];
      appMessage.method     = method;

      let deferred = new Deferred(appMessage.request_id);

     
      const skipLogMethods = [

      ]
      const displayLogMethod = [
       // 'SessionSetTrackVolumeData'
      ]
      
      if(skipLogMethods.length > 0 && skipLogMethods.includes(prop)){
      }else{
        if(displayLogMethod.includes(prop)){
          logger.log(
            "[asyncJamClient] diverting to backend:",
            prop,
            appMessage
          );
        }
      }
      
      

      if (jkfrontendchannel) {
        try {
          jkfrontendchannel.receiveText(JSON.stringify(appMessage));
          deferredQueue.push(deferred);
          return deferred.promise;
        } catch (e) {
          logger.error(
            "[asyncJamClient] Native app not connected",
            e.message
          );
          deferred.reject('Native app not connected')
          return deferred.promise;
        }
      } else {
        logger.info(
          "[asyncJamClient] jkfrontendchannel is not ready yet"
        );
        deferred.reject('frontendchannel is not ready yet')
        return deferred.promise;
      }
    }

    function setupAsyncProxy() {
      let proxy;
      const handler = {
        get(target, prop, receiver) {
          
          //const origMethod = target[prop];
          //console.log('[asyncJamClient] get:', target, prop);

          return function (...args) {
            // let result = origMethod.apply(this, args);
            // console.log(prop + JSON.stringify(args)+ ' -> ' + JSON.stringify(result));
            // return result;
            return sendWhenReady(prop, arguments)
            
          };
        },
      };
      proxy = new Proxy(self, handler);
      logger.log(
        "[asyncJamClient] Connected to WebChannel, ready to send/receive messages!"
      );
      return proxy;
    }

    setupWebSocketConnection();
    return setupAsyncProxy();
  };
})(window, jQuery);
