Search code examples
javascriptwpfpowerbipowerbi-embedded

Reload local file in JavaScript


I am a javascript novice. I mainly write code in c# so I'm every green at this.

I'm attempting to embed a PowerBI embedded report into a WPF application. I can do this, I have the report showing correctly and the data refreshing on a timer.

However the access token which is loaded with the page expires after a period of time. I'm updating the token into a local file and reading that local file when the HTML page loads.

I don't want to have to reload the HTML page in the WPF web control to refresh the access token, I want to reload the file's contents and update the PowerBI access token in live use.

When I attempt to reload the new token on a timer, the variable holding the token doesn't update. It's still using the new token.

How can I reload the contents of the 'data.json' file to update the PowerBI 'report.setAccessToken'?

see "myMethod".

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Power BI </title>
    <!-- Include Power BI JavaScript library -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js"></script>
  <script src="https://microsoft.github.io/PowerBI-JavaScript/demo/node_modules/powerbi-client/dist/powerbi.js"></script>
</head>
<body>

    <div id="embedContainer" style="height: 1080px;"></div>
    <script src="app.js" async></script>

    <!-- File which contains the access token -->
    <script type="text/javascript" src="data.json" id="data"></script> 

</body>
</html>

app.js

let loadedResolve, reportLoaded = new Promise((res, rej) => { loadedResolve = res; });
let renderedResolve, reportRendered = new Promise((res, rej) => { renderedResolve = res; });

models = window['powerbi-client'].models;

function embedPowerBIReport() {
    
    var mydata = JSON.parse(data)
    let accessToken = mydata[0].accessToken;

    // Read embed URL
    let embedUrl = "https://app.powerbi.com/reportEmbed?reportId=xxxxx";

    // Read report Id
    let embedReportId = "xxxx";

    // Read embed type from radio
    let tokenType = "Bearer";

    // We give All permissions to demonstrate switching between View and Edit mode and saving report.
    let permissions = models.Permissions.All;

    // Create the embed configuration object for the report
    let config = {
        type: 'report',
        tokenType: tokenType == '0' ? models.TokenType.Aad : models.TokenType.Embed,
        accessToken: accessToken,
        embedUrl: embedUrl,
        id: embedReportId,
        permissions: permissions,
        settings: {
            panes: {
                filters: {
                    visible: false
                },
                pageNavigation: {
                    visible: false
                }
            },
            bars: {
                statusBar: {
                    visible: false
                }
            }
        }
    };

    // Get a reference to the embedded report HTML element
    let embedContainer = $('#embedContainer')[0];

    // Embed the report and display it within the div container.
    report = powerbi.embed(embedContainer, config);

    // report.off removes all event handlers for a specific event
    report.off("loaded");

    // report.on will add an event handler
    report.on("loaded", function () {
        loadedResolve();
        report.off("loaded");
    });

    // report.off removes all event handlers for a specific event
    report.off("error");

    report.on("error", function (event) {
        console.log(event.detail);
    });

    // report.off removes all event handlers for a specific event
    report.off("rendered");

    // report.on will add an event handler
    report.on("rendered", function () {
        renderedResolve();
        report.off("rendered");
    });

    window.setInterval(function () {
        report.refresh();
    }, 30 * 1000);

}

setInterval(myMethod, 20000);

function myMethod( )
{
     var mydata = JSON.parse(data)
     accessToken = mydata[0].accessToken;
     report.setAccessToken(accessToken);
     
     
     alert(mydata[0].accessToken);

}

async function loadReport() {

    embedPowerBIReport();
    
    await reportLoaded;

    await reportRendered;
    
}

loadReport();

data.json

data = '[{"accessToken" : "xxxxxxx"}]';

Solution

  • I learnt more about JS and decided to create a web socket connection between my WFP desktop application and the JS loading and refreshing the report.

    The socket server gets a new token when the JS queries it. The JS then updates the token used by PowerBI.

    It's a POC at the moment, code to be improved but this is the working example.

    C#

    public class LocalWebSocketServer
    {
        public async Task StartServer(string ipAddress, int port)
        {
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add($"http://{ipAddress}:{port}/");
            listener.Start();
    
            Console.WriteLine("Server started. Waiting for connections...");
    
            while (true)
            {
                HttpListenerContext context = await listener.GetContextAsync();
                if (context.Request.IsWebSocketRequest)
                {
                    ProcessWebSocketRequest(context);
                }
                else
                {
                    context.Response.StatusCode = 400;
                    context.Response.Close();
                }
            }
        }
    
        private async Task ProcessWebSocketRequest(HttpListenerContext context)
        {
            HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
            WebSocket socket = webSocketContext.WebSocket;
    
            // Handle incoming messages
            byte[] buffer = new byte[1024];
            while (socket.State == WebSocketState.Open)
            {
                WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    
                if (result.MessageType == WebSocketMessageType.Text)
                {
                    string receivedMessage = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
                    Console.WriteLine($"Received message: {receivedMessage}");
    
                    var r = await EmbedService.GetEmbedParams(ConfigValidatorService.WorkspaceId, ConfigValidatorService.ReportId);
    
                    Console.WriteLine($"Sending message: {r.EmbedToken.Token}");
                    string messageback = r.EmbedToken.Token;
                    byte[] messagebackBytes = System.Text.Encoding.UTF8.GetBytes(messageback);
                    await socket.SendAsync(new ArraySegment<byte>(messagebackBytes), WebSocketMessageType.Text, true, CancellationToken.None);
    
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                }
            }
        }
    }
    

    JS

    //the token that will be used to authenticate the account
    let authToken;
    
    //the socket that will be used to communicate with the server
    let socket = new WebSocket("ws://localhost:3000");
    
    //checks if report is loaded - defines flow control on what do to when the auth token is received
    let isReportLoaded = false;
    
    //when connected send a message to the server to get the auth token
    socket.onopen = function (e) {
       console.log("Connection established");
       socket.send("GET");
       console.log("GET sent");
    }
    
    //token update interval - every 15 minutes
    setInterval(function() {
        socket.send("GET");
    }, 900*1000);
    
    
    //when a message is received from the server, set the auth token and load the report - also update the auth token if the report is already loaded
    socket.onmessage = function (event) {
    
        authToken = event.data;
        console.log("Token set: " + event.data);
    
        if (isReportLoaded === false)
        {
            loadReport();
        } else {
        report.setAccessToken(event.data);   
        }
    
    };
    
    
    //load the report   
    async function loadReport() {
    
     embedPowerBIReport();
     
     await reportLoaded;
     console.log("Report loaded");
    
     await reportRendered;
     console.log("Report Rendered");
     
     isReportLoaded = true;
    
    }