Example #1
0
        public async Task TestProcAsync(Func <JsonRpc, CancellationToken, Task> mainFunc)
        {
            var cancellationTokenSrc = new CancellationTokenSource();
            var cancellationToken    = cancellationTokenSrc.Token;

            using (var socket = new ClientWebSocket())
            {
                socket.Options.RemoteCertificateValidationCallback = (a, b, c, d) => true;

                var uri    = new Uri(LyraGlobal.SelectNode(NetworkId));
                var wssUrl = $"wss://{uri.Host}:{uri.Port}/api/v1/socket";

                await socket.ConnectAsync(new Uri(wssUrl), cancellationToken);

                using (var jsonRpc = new JsonRpc(new WebSocketMessageHandler(socket)))
                {
                    try
                    {
                        jsonRpc.AddLocalRpcMethod("Sign", new Func <string, string, string, Task <string[]> >(
                                                      async(type, msg, accountId) =>
                        {
                            var sign = await SignMessageAsync(msg);
                            return(new string[] { "p1393", sign });
                        }
                                                      ));

                        jsonRpc.AddLocalRpcMethod("Notify", new Action <JObject>(
                                                      (newsObj) =>
                        {
                            var news = newsObj.ToObject <News>();
                            if (news.catalog == "Receiving")
                            {
                                RecvNotify(news.content as JObject);
                            }
                        }
                                                      ));

                        jsonRpc.StartListening();

                        await mainFunc(jsonRpc, cancellationToken);

                        cancellationTokenSrc.Cancel();
                        await jsonRpc.Completion.WithCancellation(cancellationToken);
                    }
                    catch (OperationCanceledException)
                    {
                        // Closing is initiated by Ctrl+C on the client.
                        // Close the web socket gracefully -- before JsonRpc is disposed to avoid the socket going into an aborted state.
                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closing", CancellationToken.None);

                        //throw;
                    }
                    catch
                    {
                        throw;
                    }
                }
            }
        }
        static async Task MainAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine("Connecting to web socket...");
            using (var socket = new ClientWebSocket())
            {
                await socket.ConnectAsync(new Uri("wss://localhost:44392/socket"), cancellationToken);

                Console.WriteLine("Connected to web socket. Establishing JSON-RPC protocol...");
                using (var jsonRpc = new JsonRpc(new WebSocketMessageHandler(socket)))
                {
                    try
                    {
                        jsonRpc.AddLocalRpcMethod("Tick", new Action <int>(tick => Console.WriteLine($"Tick {tick}!")));
                        jsonRpc.StartListening();
                        Console.WriteLine("JSON-RPC protocol over web socket established.");
                        int result = await jsonRpc.InvokeWithCancellationAsync <int>("Add", new object[] { 1, 2 }, cancellationToken);

                        Console.WriteLine($"JSON-RPC server says 1 + 2 = {result}");

                        // Request notifications from the server.
                        await jsonRpc.NotifyAsync("SendTicksAsync");

                        await jsonRpc.Completion.WithCancellation(cancellationToken);
                    }
                    catch (OperationCanceledException)
                    {
                        // Closing is initiated by Ctrl+C on the client.
                        // Close the web socket gracefully -- before JsonRpc is disposed to avoid the socket going into an aborted state.
                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closing", CancellationToken.None);

                        throw;
                    }
                }
            }
        }
Example #3
0
        /// <summary>
        /// RPCに接続する
        /// </summary>
        /// <param name="ws">WebSocket</param>
        /// <returns></returns>
        private async Task ConnectRpcAsync(ClientWebSocket ws)
        {
            Rpc = new JsonRpc(new WebSocketMessageHandler(ws));

            Rpc.Disconnected += (s, e) =>
            {
                // 切断されたときの処理
                if (Logger != null)
                {
                    Logger.LogWarning($"切断されました {ToString()}");
                    isWebSocketConnected = false;
                    var t = RunAsync();
                }
            };
            Rpc.AddLocalRpcMethod(ChannelMessage, new Action <JToken, CancellationToken>((@params, cancellationToken) =>
            {
                // 受信したときの処理
                var p = @params as dynamic;

                // 受信したメッセージ(json)をイベントに送る
                OnGetMessage(new TextEventArgs(p.message.ToString()));
            }));

            Rpc.StartListening();

            await Rpc.InvokeWithParameterObjectAsync <object>("subscribe", new { channel = ChannelName });
        }
Example #4
0
        internal LanguageServerTarget(
            AbstractRequestDispatcherFactory requestDispatcherFactory,
            JsonRpc jsonRpc,
            ICapabilitiesProvider capabilitiesProvider,
            LspWorkspaceRegistrationService workspaceRegistrationService,
            LspMiscellaneousFilesWorkspace?lspMiscellaneousFilesWorkspace,
            IGlobalOptionService globalOptions,
            IAsynchronousOperationListenerProvider listenerProvider,
            ILspLogger logger,
            ImmutableArray <string> supportedLanguages,
            string?clientName,
            WellKnownLspServerKinds serverKind)
        {
            _requestDispatcher = requestDispatcherFactory.CreateRequestDispatcher(serverKind);

            _capabilitiesProvider = capabilitiesProvider;
            _logger = logger;

            _jsonRpc = jsonRpc;
            _jsonRpc.AddLocalRpcTarget(this);
            _jsonRpc.Disconnected += JsonRpc_Disconnected;

            _listener   = listenerProvider.GetListener(FeatureAttribute.LanguageServer);
            _clientName = clientName;

            _queue = new RequestExecutionQueue(
                logger,
                workspaceRegistrationService,
                lspMiscellaneousFilesWorkspace,
                globalOptions,
                supportedLanguages,
                serverKind);
            _queue.RequestServerShutdown += RequestExecutionQueue_Errored;

            var entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.EntryPointAsync));

            Contract.ThrowIfNull(entryPointMethod, $"{typeof(DelegatingEntryPoint).FullName} is missing method {nameof(DelegatingEntryPoint.EntryPointAsync)}");

            foreach (var metadata in _requestDispatcher.GetRegisteredMethods())
            {
                // Instead of concretely defining methods for each LSP method, we instead dynamically construct
                // the generic method info from the exported handler types.  This allows us to define multiple handlers for the same method
                // but different type parameters.  This is a key functionality to support TS external access as we do not want to couple
                // our LSP protocol version dll to theirs.
                //
                // We also do not use the StreamJsonRpc support for JToken as the rpc method parameters because we want
                // StreamJsonRpc to do the deserialization to handle streaming requests using IProgress<T>.
                var delegatingEntryPoint = new DelegatingEntryPoint(metadata.MethodName, this);

                var genericEntryPointMethod = entryPointMethod.MakeGenericMethod(metadata.RequestType, metadata.ResponseType);

                _jsonRpc.AddLocalRpcMethod(genericEntryPointMethod, delegatingEntryPoint, new JsonRpcMethodAttribute(metadata.MethodName)
                {
                    UseSingleObjectParameterDeserialization = true
                });
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MessageFormatterEnumerableTracker"/> class.
        /// </summary>
        /// <param name="jsonRpc">The <see cref="JsonRpc"/> instance that may be used to send or receive RPC messages related to <see cref="IAsyncEnumerable{T}"/>.</param>
        /// <param name="formatterState">The formatter that owns this tracker.</param>
        public MessageFormatterEnumerableTracker(JsonRpc jsonRpc, IJsonRpcFormatterState formatterState)
        {
            Requires.NotNull(jsonRpc, nameof(jsonRpc));
            Requires.NotNull(formatterState, nameof(formatterState));

            this.jsonRpc        = jsonRpc;
            this.formatterState = formatterState;

            jsonRpc.AddLocalRpcMethod(NextMethodName, OnNextAsyncMethodInfo, this);
            jsonRpc.AddLocalRpcMethod(DisposeMethodName, OnDisposeAsyncMethodInfo, this);
            this.formatterState = formatterState;

            // We don't offer a way to remove these handlers because this object should has a lifetime closely tied to the JsonRpc object anyway.
            IJsonRpcFormatterCallbacks callbacks = jsonRpc;

            callbacks.RequestTransmissionAborted += (s, e) => this.CleanUpResources(e.RequestId);
            callbacks.ResponseReceived           += (s, e) => this.CleanUpResources(e.RequestId);
        }
 private void AddLocalRpcMethod(string localMethodName, string rpcMethodName)
 {
     _rpc.AddLocalRpcMethod(
         handler: typeof(MsSqlServiceClient).GetMethod(localMethodName),
         target: this,
         methodRpcSettings: new JsonRpcMethodAttribute(rpcMethodName)
     {
         UseSingleObjectParameterDeserialization = true
     });
 }
Example #7
0
        internal LanguageServerTarget(
            AbstractLspServiceProvider lspServiceProvider,
            JsonRpc jsonRpc,
            ICapabilitiesProvider capabilitiesProvider,
            IAsynchronousOperationListenerProvider listenerProvider,
            ILspLogger logger,
            ImmutableArray <string> supportedLanguages,
            WellKnownLspServerKinds serverKind)
        {
            _capabilitiesProvider = capabilitiesProvider;
            _logger = logger;

            _jsonRpc = jsonRpc;
            _jsonRpc.AddLocalRpcTarget(this);
            _jsonRpc.Disconnected += JsonRpc_Disconnected;

            _listener = listenerProvider.GetListener(FeatureAttribute.LanguageServer);

            // Add services that require base dependencies (jsonrpc) or are more complex to create to the set manually.
            _lspServices = lspServiceProvider.CreateServices(serverKind, ImmutableArray.Create(
                                                                 CreateLspServiceInstance <ILanguageServerNotificationManager>(new LanguageServerNotificationManager(_jsonRpc)),
                                                                 CreateLspServiceInstance(logger),
                                                                 CreateLspServiceInstance <IClientCapabilitiesProvider>(this)));

            _queue = new RequestExecutionQueue(
                supportedLanguages,
                serverKind,
                _lspServices);
            _queue.RequestServerShutdown += RequestExecutionQueue_Errored;

            _requestDispatcher = _lspServices.GetRequiredService <RequestDispatcher>();

            var entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.EntryPointAsync));

            Contract.ThrowIfNull(entryPointMethod, $"{typeof(DelegatingEntryPoint).FullName} is missing method {nameof(DelegatingEntryPoint.EntryPointAsync)}");

            foreach (var metadata in _requestDispatcher.GetRegisteredMethods())
            {
                // Instead of concretely defining methods for each LSP method, we instead dynamically construct the
                // generic method info from the exported handler types.  This allows us to define multiple handlers for
                // the same method but different type parameters.  This is a key functionality to support TS external
                // access as we do not want to couple our LSP protocol version dll to theirs.
                //
                // We also do not use the StreamJsonRpc support for JToken as the rpc method parameters because we want
                // StreamJsonRpc to do the deserialization to handle streaming requests using IProgress<T>.
                var delegatingEntryPoint = new DelegatingEntryPoint(metadata.MethodName, this);

                var genericEntryPointMethod = entryPointMethod.MakeGenericMethod(metadata.RequestType, metadata.ResponseType);

                _jsonRpc.AddLocalRpcMethod(genericEntryPointMethod, delegatingEntryPoint, new JsonRpcMethodAttribute(metadata.MethodName)
                {
                    UseSingleObjectParameterDeserialization = true
                });
            }
Example #8
0
        static async Task Main(string[] args)
        {
            Console.WriteLine("Kurento Echo Test Client");
            logger = AddConsoleLogger();

            CancellationTokenSource cts = new CancellationTokenSource();

            var ws = new ClientWebSocket();
            await ws.ConnectAsync(new Uri(KURENTO_JSONRPC_URL), cts.Token);

            logger.LogDebug($"Successfully connected web socket client to {KURENTO_JSONRPC_URL}.");

            try
            {
                using (var jsonRpc = new JsonRpc(new WebSocketMessageHandler(ws)))
                {
                    jsonRpc.AddLocalRpcMethod("ping", new Action(() =>
                    {
                        logger.LogDebug($"Ping received");
                    }
                                                                 ));

                    jsonRpc.AddLocalRpcMethod("onEvent", new Action <KurentoEvent>((evt) =>
                    {
                        logger.LogDebug($"Event received type={evt.type}, source={evt.data.source}");
                    }
                                                                                   ));

                    jsonRpc.StartListening();

                    // Check the server is there.
                    var pingResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.ping.ToString(),
                        new { interval = KEEP_ALIVE_INTERVAL_MS },
                        cts.Token);

                    logger.LogDebug($"Ping result={pingResult.value}.");

                    // Create a media pipeline.
                    var createPipelineResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.create.ToString(),
                        new { type = "MediaPipeline" },
                        cts.Token);

                    logger.LogDebug($"Create media pipeline result={createPipelineResult.value}, sessionID={createPipelineResult.sessionId}.");

                    var sessionID     = createPipelineResult.sessionId;
                    var mediaPipeline = createPipelineResult.value;

                    // Create a WebRTC end point.
                    var createEndPointResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.create.ToString(),
                        new
                    {
                        type = "WebRtcEndpoint",
                        constructorParams = new { mediaPipeline = mediaPipeline },
                        sessionId         = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Create WebRTC endpoint result={createEndPointResult.value}.");

                    var webRTCEndPointID = createEndPointResult.value;

                    // Connect the WebRTC end point to itself to create a loopback connection (no result for this operation).
                    await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.invoke.ToString(),
                        new
                    {
                        @object         = webRTCEndPointID,
                        operation       = "connect",
                        operationParams = new { sink = webRTCEndPointID },
                        sessionId       = sessionID
                    },
                        cts.Token);

                    // Subscribe for events from the WebRTC end point.
                    var subscribeResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.subscribe.ToString(),
                        new
                    {
                        @object   = webRTCEndPointID,
                        type      = "IceCandidateFound",
                        sessionId = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Subscribe to WebRTC endpoint subscription ID={subscribeResult.value}.");

                    var subscriptionID = subscribeResult.value;

                    subscribeResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.subscribe.ToString(),
                        new
                    {
                        @object   = webRTCEndPointID,
                        type      = "OnIceCandidate",
                        sessionId = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Subscribe to WebRTC endpoint subscription ID={subscribeResult.value}.");

                    var pc    = CreatePeerConnection();
                    var offer = pc.createOffer(null);
                    await pc.setLocalDescription(offer);

                    // Send SDP offer.
                    var processOfferResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.invoke.ToString(),
                        new
                    {
                        @object         = webRTCEndPointID,
                        operation       = "processOffer",
                        operationParams = new { offer = offer.sdp },
                        sessionId       = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"SDP answer={processOfferResult.value}.");

                    var setAnswerResult = pc.setRemoteDescription(new RTCSessionDescriptionInit
                    {
                        type = RTCSdpType.answer,
                        sdp  = processOfferResult.value
                    });

                    logger.LogDebug($"Set WebRTC peer connection answer result={setAnswerResult}.");

                    // Tell Kurento to start ICE.
                    var gatherCandidatesResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.invoke.ToString(),
                        new
                    {
                        @object   = webRTCEndPointID,
                        operation = "gatherCandidates",
                        sessionId = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Gather candidates result={gatherCandidatesResult.value}.");

                    Console.ReadLine();
                }
            }
            catch (RemoteInvocationException invokeExcp)
            {
                logger.LogError($"JSON RPC invoke exception, error code={invokeExcp.ErrorCode}, msg={invokeExcp.Message}.");
            }
        }
Example #9
0
        /// <summary>
        /// Connects to the snapserver and sets up local RPC methods
        /// </summary>
        /// <param name="ip">IP address to connect to</param>
        /// <param name="port">port to connect on</param>
        /// <returns></returns>
        public async Task ConnectAsync(string ip, int port, int timeout = 3000)
        {
            // connect
            m_Ip             = ip;
            m_Port           = port;
            ConnectionFailed = false;
            ServerData       = null;

            m_TcpClient = new TcpClient();
            try
            {
                //if (m_TcpClient.ConnectAsync(ip, port).Wait(timeout) == false)
                var connectAsync = m_TcpClient.ConnectAsync(ip, port);
                //await (m_TcpClient.ConnectAsync(ip, port)).
                if ((await Task.WhenAny(connectAsync, Task.Delay(timeout)) != connectAsync) || string.IsNullOrEmpty(ip))
                {
                    _HandleConnectionFailure();
                    return;
                }
            }
            catch (Exception e)
            {
                // logging this to debug because we don't want to spam the log file (connection gets retried indefinitely if it had previously succeeded)
                Debug("Connect exception: ", e.Message);
                _HandleConnectionFailure();
                return;
            }

            if (m_TcpClient.Connected == false)
            {
                _HandleConnectionFailure();
                return;
            }

            // attach StreamJsonRpc (NewLineDelimited)
            m_Stream = m_TcpClient.GetStream();

            // UnbatchingNewLineDelimitedMessageHandler adapted from UnbatchingWebSocketMessageHandler
            // https://github.com/microsoft/vs-streamjsonrpc/compare/master...AArnott:sampleUnbatchingMessageHandler
            m_JsonRpc = new StreamJsonRpc.JsonRpc(new UnbatchingNewLineDelimitedMessageHandler(m_Stream, m_Stream));

            //m_JsonRpc.TraceSource.Switch.Level = SourceLevels.All; // uncomment if you need detailed json-rpc logs

            // register methods (must be done before listening starts)
            m_JsonRpc.AddLocalRpcMethod("Server.OnUpdate", new Action <JsonRpcData.ServerData>((server) =>
            {
                Debug("Received Server.OnUpdate - {0}", server);
                _ServerUpdated(server);
            }));

            m_JsonRpc.AddLocalRpcMethod("Client.OnVolumeChanged", new Action <string, JsonRpcData.Volume>((id, volume) =>
            {
                Debug("Received Client.OnVolumeChanged - id {0}, volume {1}", id, volume);
                _ClientVolumeChanged(id, volume);
            }));

            m_JsonRpc.AddLocalRpcMethod("Client.OnNameChanged", new Action <string, string>((id, name) =>
            {
                Debug("Received Client.OnNameChanged - id {0}, name {1}", id, name);
                _ClientNameChanged(id, name);
            }));

            m_JsonRpc.AddLocalRpcMethod("Client.OnLatencyChanged", new Action <string, int>((id, latency) =>
            {
                Debug("Received Client.OnLatencyChanged - id {0}, latency {1}", id, latency);
                _ClientLatencyChanged(id, latency);
            }));

            m_JsonRpc.AddLocalRpcMethod("Client.OnConnect", new Action <string, JsonRpcData.Client>((id, client) =>
            {
                // when the server's been down, we keep trying to reconnect (as does every other client)
                // when reconnection succeeds, we might see other clients' OnConnect calls come in
                // before our own Server.GetStatus has completed. In that case, we queue these
                // and execute them as soon as we've received the server data
                Debug("Received Client.OnConnect - id {0}, client {1}", id, client);
                if (ServerData != null)
                {
                    _ClientConnectedOrDisconnected(id, client);
                }
                else
                {
                    m_QueuedMessages.Enqueue(() => { _ClientConnectedOrDisconnected(id, client); });
                }
            }));

            m_JsonRpc.AddLocalRpcMethod("Client.OnDisconnect", new Action <string, JsonRpcData.Client>((id, client) =>
            {
                Debug("Received Client.OnDisconnect - id {0}, client {1}", id, client);
                _ClientConnectedOrDisconnected(id, client);
            }));

            m_JsonRpc.AddLocalRpcMethod("Group.OnMute", new Action <string, bool>((id, mute) =>
            {
                Debug("Received Group.OnMute - id {0}, mute {1}", id, mute);
                _GroupMuteChanged(id, mute);
            }));

            m_JsonRpc.AddLocalRpcMethod("Group.OnNameChanged", new Action <string, string>((id, name) =>
            {
                Debug("Received Group.OnNameChanged - id {0}, name {1}", id, name);
                _GroupNameChanged(id, name);
            }));

            m_JsonRpc.AddLocalRpcMethod("Group.OnStreamChanged", new Action <string, string>((id, stream_id) =>
            {
                Debug("Received Group.OnStreamChanged - id {0}, stream_id {1}", id, stream_id);
                _GroupStreamChanged(id, stream_id);
            }));

            m_JsonRpc.AddLocalRpcMethod("Stream.OnUpdate", new Action <string, Stream>((id, stream) =>
            {
                Debug("Received Stream.OnUpdate - id {0}, stream {1}", id, stream);
                _StreamUpdated(id, stream);
            }));

            m_JsonRpc.AddLocalRpcMethod("Stream.OnProperties", new Action <string, Properties>((id, properties) =>
            {
                Debug("Received Stream.OnProperties - id {0}, properties {1}", id, properties);
                _StreamPropertiesUpdated(id, properties);
            }));

            m_JsonRpc.StartListening();
            // call Server.GetStatus to get all metadata
            await GetServerStatusAsync();

            // make sure we find out if connection drops
            _StartReconnectLoop();
        }
Example #10
0
        private async static Task OfferAsync(IHttpContext context)
        {
            var offer = await context.GetRequestDataAsync <RTCSessionDescriptionInit>();

            logger.LogDebug($"SDP Offer={offer.sdp}");

            var jsonOptions = new JsonSerializerOptions();

            jsonOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());

            CancellationTokenSource cts = new CancellationTokenSource();

            var ws = new ClientWebSocket();
            await ws.ConnectAsync(new Uri(KURENTO_JSONRPC_URL), cts.Token);

            logger.LogDebug($"Successfully connected web socket client to {KURENTO_JSONRPC_URL}.");

            try
            {
                using (var jsonRpc = new JsonRpc(new WebSocketMessageHandler(ws)))
                {
                    jsonRpc.AddLocalRpcMethod("ping", new Action(() =>
                    {
                        logger.LogDebug($"Ping received");
                    }
                                                                 ));

                    jsonRpc.AddLocalRpcMethod("onEvent", new Action <KurentoEvent>((evt) =>
                    {
                        logger.LogDebug($"Event received type={evt.type}, source={evt.data.source}");
                    }
                                                                                   ));

                    jsonRpc.StartListening();

                    // Check the server is there.
                    var pingResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.ping.ToString(),
                        new { interval = KEEP_ALIVE_INTERVAL_MS },
                        cts.Token);

                    logger.LogDebug($"Ping result={pingResult.value}.");

                    // Create a media pipeline.
                    var createPipelineResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.create.ToString(),
                        new { type = "MediaPipeline" },
                        cts.Token);

                    logger.LogDebug($"Create media pipeline result={createPipelineResult.value}, sessionID={createPipelineResult.sessionId}.");

                    var sessionID     = createPipelineResult.sessionId;
                    var mediaPipeline = createPipelineResult.value;

                    // Create a WebRTC end point.
                    var createEndPointResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.create.ToString(),
                        new
                    {
                        type = "WebRtcEndpoint",
                        constructorParams = new { mediaPipeline = mediaPipeline },
                        sessionId         = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Create WebRTC endpoint result={createEndPointResult.value}.");

                    var webRTCEndPointID = createEndPointResult.value;

                    // Connect the WebRTC end point to itself to create a loopback connection (no result for this operation).
                    await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.invoke.ToString(),
                        new
                    {
                        @object         = webRTCEndPointID,
                        operation       = "connect",
                        operationParams = new { sink = webRTCEndPointID },
                        sessionId       = sessionID
                    },
                        cts.Token);

                    // Subscribe for events from the WebRTC end point.
                    var subscribeResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.subscribe.ToString(),
                        new
                    {
                        @object   = webRTCEndPointID,
                        type      = "IceCandidateFound",
                        sessionId = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Subscribe to WebRTC endpoint subscription ID={subscribeResult.value}.");

                    var subscriptionID = subscribeResult.value;

                    subscribeResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.subscribe.ToString(),
                        new
                    {
                        @object   = webRTCEndPointID,
                        type      = "OnIceCandidate",
                        sessionId = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Subscribe to WebRTC endpoint subscription ID={subscribeResult.value}.");

                    // Send SDP offer.
                    var processOfferResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.invoke.ToString(),
                        new
                    {
                        @object         = webRTCEndPointID,
                        operation       = "processOffer",
                        operationParams = new { offer = offer.sdp },
                        sessionId       = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"SDP answer={processOfferResult.value}.");

                    RTCSessionDescriptionInit answerInit = new RTCSessionDescriptionInit
                    {
                        type = RTCSdpType.answer,
                        sdp  = processOfferResult.value
                    };

                    context.Response.ContentType = "application/json";
                    using (var responseStm = context.OpenResponseStream(false, false))
                    {
                        await JsonSerializer.SerializeAsync(responseStm, answerInit, jsonOptions);
                    }

                    // Tell Kurento to start ICE.
                    var gatherCandidatesResult = await jsonRpc.InvokeWithParameterObjectAsync <KurentoResult>(
                        KurentoMethodsEnum.invoke.ToString(),
                        new
                    {
                        @object   = webRTCEndPointID,
                        operation = "gatherCandidates",
                        sessionId = sessionID
                    },
                        cts.Token);

                    logger.LogDebug($"Gather candidates result={gatherCandidatesResult.value}.");
                }
            }
            catch (RemoteInvocationException invokeExcp)
            {
                logger.LogError($"JSON RPC invoke exception, error code={invokeExcp.ErrorCode}, msg={invokeExcp.Message}.");
            }
        }