在Blazor Server端和Twilio中再次使用该功能时遇到问题

问题描述

我已经在Blazor服务器端建立了一个网站。我正在使用Twilio Voice Api使用户能够拨打紧急电话。我已经能够成功拨打电话。当我离开页面,然后返回页面再次拨打电话时,我收到此错误

Error: Microsoft.JSInterop.JSException: Cannot read property 'setToken' of undefined
TypeError: Cannot read property 'setToken' of undefined
    at a.register (https://media.twiliocdn.com/sdk/js/client/v1.3/twilio.min.js:39:420)
    at Function.setup (https://media.twiliocdn.com/sdk/js/client/v1.3/twilio.min.js:47:126)
    at Object.setup (https://localhost:44320/js/tw.js:6:23)
    at https://localhost:44320/_framework/blazor.server.js:8:31619
    at new Promise (<anonymous>)
    at e.beginInvokeJSFromDotNet (https://localhost:44320/_framework/blazor.server.js:8:31587)
    at https://localhost:44320/_framework/blazor.server.js:1:20052
    at Array.forEach (<anonymous>)
    at e.invokeClientMethod (https://localhost:44320/_framework/blazor.server.js:1:20022)
    at e.processIncomingData (https://localhost:44320/_framework/blazor.server.js:1:18006)
   at Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation[T](String identifier,Object[] args)
   at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime,String identifier,Object[] args)
   at QUBeMyGuest.Pages.GuestArrivals.EmergencyContact.OnAfterRenderAsync(Boolean firstRender) in C:Pages\GuestArrivals\EmergencyContact.razor:line 75
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

我的api是

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Twilio.Jwt;
using Twilio.Jwt.Accesstoken;
using Twilio.Jwt.Client;
using Twilio.TwiML;
using Twilio.Types;
using System.Net.Http;


namespace api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TwilioBackEndController : ControllerBase
    {
        public readonly string AccountSid = "xxxxxx";
        public readonly string AuthToken = "xxxx";
        public readonly string AppSid = "xxxxx";
        public readonly string PhoneNumber = "xxxxx";


        [HttpGet("token")]
        public async Task<IActionResult> GetToken()
        {
            var scopes = new HashSet<IScope>
            {
                new OutgoingClientScope(AppSid),new IncomingClientScope("tester")
            };

            var capability = new ClientCapability(AccountSid,AuthToken,scopes: scopes);
            return await Task.Fromresult(Content(capability.ToJwt(),"application/jwt"));
        }

        [HttpPost("voice")]
        public async Task<IActionResult> PostVoiceRequest([FromForm] string phone)
        {
            var destination = !phone.StartsWith('+') ? $"+{phone}" : phone;

            var response = new VoiceResponse();
            var dial = new Twilio.TwiML.Voice.Dial
            {
                CallerId = PhoneNumber
            };
            dial.Number(new PhoneNumber(destination));

            response.Append(dial);

            return await Task.Fromresult(Content(response.ToString(),"application/xml"));
        }

        
    }
}

我的紧急联系页面是:

@page "/guest/emergencycall"
@using System.ComponentModel.DataAnnotations
@inject HttpClient httpClient
@using Microsoft.Extensions.DependencyInjection
@using System.Net.Http


<EditForm Model="Input" OnValidSubmit="InitiatePhoneCall">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label for="phoneNumber">Enter Phone Number:</label>
        <InputText id="phoneNumber" @bind-Value="Input.PhoneNumber"></InputText>
        <button type="submit" class="btn btn-primary" disabled="@IsDialdisabled">DIAL</button>
        <button type="button" id="endBtn" class="btn btn-primary" disabled="@IsEnddisabled" @onclick="EndPhoneCall">END</button>
        <button type="button" id="clearBtn" class="btn btn-primary" disabled="@IsCleardisabled" @onclick="ClearPhoneNumber">CLEAR</button>
    </p>
</EditForm>

<hr />

@if (Logs.Count == 0)
{
    <p>No Logs available yet</p>
}
else
{
    <ul>
        @foreach (var log in Logs)
        {

            <li>@log</li>
        }
    </ul>
}

@code {
    private string _tokenUrl = "https://4b4cd1derdsb.ngrok.io/api/twiliobackend";
    private bool appSetupRun = false;

    protected bool IsDialdisabled { get; set; } = false;
    protected bool IsEnddisabled { get { return !IsDialdisabled; } }

    protected bool IsCleardisabled { get { return string.IsNullOrEmpty(Input.PhoneNumber); } }
    protected List<string> Logs { get; set; } = new List<string>();

    protected InputModel Input { get; set; } = new InputModel();
    [Inject]
    protected IJSRuntime JSRuntime { get; set; }
    [Inject]
    protected IHttpClientFactory HttpClientFactory { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && !appSetupRun)
        {
            var token = await GetClientToken();
            await JSRuntime.InvokeVoidAsync("appFunctions.setup",token);
            appSetupRun = true;
        }
    }

    protected async Task InitiatePhoneCall()
    {
        IsDialdisabled = true;
        await LogMessage($"Calling the number {Input.PhoneNumber}");
        await JSRuntime.InvokeVoidAsync("appFunctions.placeCall",Input.PhoneNumber);
        await LogMessage($"Called the number {Input.PhoneNumber}");
        StateHasChanged();
    }

    protected async Task EndPhoneCall()
    {
        IsDialdisabled = false;
        await LogMessage($"Ending the call to {Input.PhoneNumber}");
        await JSRuntime.InvokeVoidAsync("appFunctions.endCall");
        await LogMessage($"Ended the call to {Input.PhoneNumber}");
        StateHasChanged();

    }

    protected async Task ClearPhoneNumber()
    {
        await LogMessage("Clearing the phone number entry");
        Input.PhoneNumber = string.Empty;
        await LogMessage("Cleared the phone number entry");
        StateHasChanged();
    }

    private async Task<string> GetClientToken()
    {
        var uri = new Uri(_tokenUrl);

        using var client = HttpClientFactory.CreateClient();
        var response = await client.GetAsync(uri);

        response.EnsureSuccessstatusCode();
        return await response.Content.ReadAsstringAsync();
    }

    [JSInvokable]
    public async Task LogMessage(string message)
    {
        Logs.Add($"{DateTimeOffset.Now} - {message}");
        await Task.CompletedTask;
    }

    public class InputModel
    {
        [required]
        [Phone(ErrorMessage = "Please enter your phone number in a proper format")]
        public string PhoneNumber { get; set; }
    }
}

和我的JavaScript:

window.appFunctions = {
    setup: function (token) {
        console.log('Getting connected');

        // Setup Twilio Device
        Twilio.Device.setup(token);

        Twilio.Device.ready(() => {
            console.log('We are connected and ready to do the thing');
        });

        Twilio.Device.error((err) => {
            console.error('This should not have been reached. We need to do something here');
            console.error(err);
        });
    },placeCall: function (destination) {
        console.log(`Calling ${destination}`);
        Twilio.Device.connect({ phone: destination });
        console.log(`Successfully called ${destination}`);
    },endCall: function () {
        console.log('Ending the call');
        Twilio.Device.disconnectAll();
        console.log('Successfully ended the call');
    }
};

任何建议都会很棒

解决方法

根据我的一点经验 当 Microsoft.JSInterop.JSException 发生时,通常是您调用 javascript 时 在您的代码中,请验证从 JSRuntime 调用的 javascript 函数

   [Inject]
   protected IJSRuntime JSRuntime { get; set; }
   ...
   await JSRuntime.InvokeVoidAsync("appFunctions.setup",token);
   await JSRuntime.InvokeVoidAsync("appFunctions.placeCall",Input.PhoneNumber);
   await JSRuntime.InvokeVoidAsync("appFunctions.endCall");

并且因为 setToken 关键字总是用于 web 和 api 之间的通信设置的函数名称。我猜在 javascript 函数 appFunctions.setup 中,返回页面后某些对象变为空,因此无法初始化。 正如你在

中看到的
TypeError: Cannot read property 'setToken' of undefined
    at a.register (https://media.twiliocdn.com/sdk/js/client/v1.3/twilio.min.js:39:420)
    at Function.setup (https://media.twiliocdn.com/sdk/js/client/v1.3/twilio.min.js:47:126)
    at Object.setup (https://localhost:44320/js/tw.js:6:23)

文件 tw.js(可能是第 23 行?)中的 javascript 函数将使用一些空对象来设置通信?