diff --git a/Go/main.go b/Go/main.go deleted file mode 100644 index b9d0423..0000000 --- a/Go/main.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "os" - "io" - "log" - "net/http" - "strconv" - "strings" - "time" - "fmt" -) - -var embedConfig map[string]interface{} - -type EmbedConfig struct { - DashboardId string `json:"DashboardId"` - ServerUrl string `json:"ServerUrl"` - EmbedType string `json:"EmbedType"` - Environment string `json:"Environment"` - SiteIdentifier string `json:"SiteIdentifier"` -} - -func main() { - http.HandleFunc("/authorizationServer", authorizationServer) - http.HandleFunc("/getServerDetails", getServerDetails) - fmt.Println("Go server is running on port 8086") - log.Fatal(http.ListenAndServe(":8086", nil)) -} - -func getServerDetails(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Context-Type", "application/x-www-form-urlencoded") - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type") - - data, err := os.ReadFile("embedConfig.json") - if err != nil { - log.Fatal("Error: embedConfig.json file not found.") - } - - err = json.Unmarshal(data, &embedConfig) - - // Create a custom struct to hold the specific properties you want to return. - clientEmbedConfigData := EmbedConfig{ - DashboardId: embedConfig["DashboardId"].(string), - ServerUrl: embedConfig["ServerUrl"].(string), - SiteIdentifier: embedConfig["SiteIdentifier"].(string), - EmbedType: embedConfig["EmbedType"].(string), - Environment: embedConfig["Environment"].(string), - } - - jsonResponse, err := json.Marshal(clientEmbedConfigData) - w.Write(jsonResponse) -} - -func authorizationServer(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Context-Type", "application/x-www-form-urlencoded") - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "POST") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type") - body, err := io.ReadAll(r.Body) - if err != nil { - log.Fatalln(err) - } - if len(body) > 0 { - if queryString, err := unmarshal(string(body)); err != nil { - log.Println("error converting", err) - } else { - userEmail := embedConfig["UserEmail"].(string) - serverAPIUrl := queryString.(map[string]interface{})["dashboardServerApiUrl"].(string) - embedQueryString := queryString.(map[string]interface{})["embedQuerString"].(string) - embedQueryString += "&embed_user_email=" + userEmail - timeStamp := time.Now().Unix() - embedQueryString += "&embed_server_timestamp=" + strconv.FormatInt(timeStamp, 10) - signatureString, err := getSignatureUrl(embedQueryString) - embedDetails := "/embed/authorize?" + embedQueryString + "&embed_signature=" + signatureString - query := serverAPIUrl + embedDetails - result, err := http.Get(query) - if err != nil { - log.Println(err) - } - response, err := io.ReadAll(result.Body) - if err != nil { - log.Fatalln(err) - } - w.Write(response) - } - } -} - -func getSignatureUrl(queryData string) (string, error) { - embedSecret := embedConfig["EmbedSecret"].(string) - encoding := ([]byte(embedSecret)) - messageBytes := ([]byte(queryData)) - hmacsha1 := hmac.New(sha256.New, encoding) - hmacsha1.Write(messageBytes) - sha := base64.StdEncoding.EncodeToString(hmacsha1.Sum(nil)) - return sha, nil -} - -func unmarshal(data string) (interface{}, error) { - var iface interface{} - decoder := json.NewDecoder(strings.NewReader(data)) - decoder.UseNumber() - if err := decoder.Decode(&iface); err != nil { - return nil, err - } - return iface, nil -} diff --git a/Go/tokengeneration.go b/Go/tokengeneration.go new file mode 100644 index 0000000..fc51e1b --- /dev/null +++ b/Go/tokengeneration.go @@ -0,0 +1,175 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "bytes" + "strings" + "sync" +) + +var embedConfig map[string]interface{} +var embedConfigOnce sync.Once +var embedConfigErr error + +type EmbedConfig struct { + DashboardId string `json:"DashboardId"` + ServerUrl string `json:"ServerUrl"` + EmbedType string `json:"EmbedType"` + Environment string `json:"Environment"` + SiteIdentifier string `json:"SiteIdentifier"` +} + +func main() { + http.HandleFunc("/tokenGeneration", tokenGeneration) + http.HandleFunc("/getServerDetails", getServerDetails) + fmt.Println("Go server is running on port 8086") + log.Fatal(http.ListenAndServe(":8086", nil)) +} + +func getServerDetails(w http.ResponseWriter, r *http.Request) { + setCORS(w, "GET") + + if err := loadEmbedConfig(); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + return + } + + clientEmbedConfigData := EmbedConfig{ + DashboardId: getConfigStr("DashboardId"), + ServerUrl: getConfigStr("ServerUrl"), + SiteIdentifier: getConfigStr("SiteIdentifier"), + EmbedType: getConfigStr("EmbedType"), + Environment: getConfigStr("Environment"), + } + + if clientEmbedConfigData.ServerUrl == "" || clientEmbedConfigData.SiteIdentifier == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": "ServerUrl and SiteIdentifier are required in embedConfig.json"}) + return + } + + jsonResponse, err := json.Marshal(clientEmbedConfigData) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{"error": "failed to marshal response"}) + return + } + w.Write(jsonResponse) +} + +func tokenGeneration(w http.ResponseWriter, r *http.Request) { + setCORS(w, "POST, OPTIONS") + + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + if err := loadEmbedConfig(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + serverUrl := getConfigStr("ServerUrl", "serverurl") + siteIdentifier := getConfigStr("SiteIdentifier", "siteidentifier") + email := getConfigStr("UserEmail", "email") + embedSecret := getConfigStr("EmbedSecret", "embedsecret") + dashboardId := getConfigStr("DashboardId", "dashboardId") + + if serverUrl == "" || siteIdentifier == "" { + http.Error(w, "ServerUrl and SiteIdentifier are required", http.StatusBadRequest) + return + } + + embedDetails := map[string]interface{}{ + "serverurl": serverUrl, + "siteidentifier": siteIdentifier, + "email": email, + "embedsecret": embedSecret, + "dashboard": map[string]string{ + "id": dashboardId, + }, + } + + payload, err := json.Marshal(embedDetails) + if err != nil { + http.Error(w, "failed to marshal embedDetails", http.StatusInternalServerError) + return + } + + requestUrl := fmt.Sprintf("%s/api/%s/embed/authorize", strings.TrimRight(embedDetails["serverurl"].(string), "/"), embedDetails["siteidentifier"].(string)) + resp, err := http.Post(requestUrl, "application/json", bytes.NewReader(payload)) + if err != nil { + http.Error(w, err.Error(), http.StatusBadGateway) + return + } + defer resp.Body.Close() + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + http.Error(w, "failed to read response", http.StatusBadGateway) + return + } + + var respObj map[string]interface{} + if err := json.Unmarshal(respBytes, &respObj); err != nil { + w.WriteHeader(resp.StatusCode) + w.Write(respBytes) + return + } + + d, ok := respObj["Data"].(map[string]interface{}) + if !ok { + http.Error(w, "invalid response shape: Data field missing", http.StatusBadGateway) + return + } + token, ok := d["access_token"].(string) + if !ok || token == "" { + http.Error(w, "access_token not found in Data", http.StatusBadGateway) + return + } + w.Write([]byte(token)) +} + +// loadEmbedConfig reads and parses embedConfig.json once and caches the result. +func loadEmbedConfig() error { + embedConfigOnce.Do(func() { + data, err := os.ReadFile("embedConfig.json") + if err != nil { + embedConfigErr = fmt.Errorf("embedConfig.json file not found: %w", err) + return + } + s := strings.TrimPrefix(string(data), "\uFEFF") + var cfg map[string]interface{} + if err := json.Unmarshal([]byte(strings.TrimSpace(s)), &cfg); err != nil { + embedConfigErr = fmt.Errorf("invalid embedConfig.json: %w", err) + return + } + embedConfig = cfg + }) + return embedConfigErr +} + +// setCORS writes common CORS and JSON headers +func setCORS(w http.ResponseWriter, methods string) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", methods) + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") +} + +// getConfigStr returns the first non-empty string value from embedConfig for provided keys +func getConfigStr(keys ...string) string { + for _, k := range keys { + if s, ok := embedConfig[k].(string); ok && s != "" { + return s + } + } + return "" +} \ No newline at end of file diff --git a/README.md b/README.md index 8a72f32..13cad58 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,11 @@ This Bold BI React with Go sample contains the Dashboard embedding sample. In th ## Run a Sample Using Command Line Interface -* Open the command line interface and navigate to the specified file [location](https://github.com/boldbi/react-with-go-sample/tree/master/Go) where the project is located. +* Open the **command line interface** and navigate to the specified file [location](https://github.com/boldbi/react-with-go-sample/tree/master/Go) where the project is located. -* Run the back-end `Go` sample using the following command `go run main.go`. +* Run the back-end `Go` sample using the following command `go run tokengeneration.go`, it will display a URL in the **command line interface**, typically something like (e.g., ). Copy this URL and paste it by appending `/getServerDetails` into your default web browser. -* Open the command line interface and navigate to the specified file [location](https://github.com/boldbi/react-with-go-sample/tree/master/React) where the project is located. +* Open the **command line interface** and navigate to the specified file [location](https://github.com/boldbi/react-with-go-sample/tree/master/React) where the project is located. * To install all dependent packages, use the following command `npm install`. @@ -51,20 +51,20 @@ This Bold BI React with Go sample contains the Dashboard embedding sample. In th ### Run a Sample Using Visual Studio Code -* Open the `Go` sample in Visual Studio Code. +* Open the `Go` sample in **Visual Studio Code**. -* Install the extension `Go` in Visual Studio Code. Please refer to the following image. +* Install the extension `Go` in **Visual Studio Code**. Please refer to the following image. ![Extension](/images/go-extension.png) -* Run the back-end `Go` sample using the following command `go run main.go`. +* Run the back-end `Go` sample using the following command `go run tokengeneration.go`, it will display a URL in the **command line interface**, typically something like (e.g., ). Copy this URL and paste it by appending `/getServerDetails` into your default web browser. -* Open the `React` sample in a new window of Visual Studio Code. +* Open the `React` sample in a new window of **Visual Studio Code**. * To install all dependent packages, use the following command `npm install`. * Finally, run the application using the command `npm start`. After executing the command, the application will automatically launch in the default browser. You can access it at the specified port number (e.g., ). -![dashboard image](/images/dashboard.png) + ![dashboard image](/images/dashboard.png) Please refer to the [help documentation](https://help.boldbi.com/embedding-options/embedding-sdk/samples/react-with-go/#how-to-run-the-sample?utm_source=github&utm_medium=backlinks) to know how to run the sample. diff --git a/React/package.json b/React/package.json index d336d1d..680f0e3 100644 --- a/React/package.json +++ b/React/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@boldbi/boldbi-embedded-sdk": "^8.2.22", + "@boldbi/boldbi-embedded-sdk": "^15.1.65", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/React/src/Dashboard/Dashboard.js b/React/src/Dashboard/Dashboard.js index ead4039..4674e37 100644 --- a/React/src/Dashboard/Dashboard.js +++ b/React/src/Dashboard/Dashboard.js @@ -2,8 +2,8 @@ import React from 'react'; import '../index'; import { BoldBI } from '@boldbi/boldbi-embedded-sdk'; -//Url of the authorizationserver action in the Go application(http://localhost:8086/authorizationserver). Learn more about authorize server [here](https://help.syncfusion.com/bold-bi/embedded-bi/javascript/authorize-server) -const authorizationUrl = "http://localhost:8086/authorizationServer"; +//Url of the tokenGeneration action in tokengeneration.go +const tokenGenerationUrl = "http://localhost:8086/tokenGeneration"; class Dashboard extends React.Component { constructor(props) { @@ -12,19 +12,34 @@ class Dashboard extends React.Component { this.BoldBiObj = new BoldBI(); }; - renderDashboard(embedConfig) { - this.dashboard = BoldBI.create({ - serverUrl: embedConfig.ServerUrl + "/" + embedConfig.SiteIdentifier, - dashboardId: embedConfig.DashboardId, - embedContainerId: "dashboard", - width: "100%", - height: window.innerHeight + 'px', - authorizationServer: { - url: authorizationUrl + getEmbedToken() { + return fetch(tokenGenerationUrl, { // Backend application URL + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({}) + }) + .then(response => { + if (!response.ok) throw new Error("Token fetch failed"); + return response.text(); + }); } - }); - this.dashboard.loadDashboard(); - } + + renderDashboard(data) { + this.getEmbedToken() + .then(accessToken => { + const dashboard = BoldBI.create({ + serverUrl: data.ServerUrl + "/" + data.SiteIdentifier, + dashboardId: data.DashboardId, + embedContainerId: "dashboard", + embedToken: accessToken + }); + + dashboard.loadDashboard(); + }) + .catch(err => { + console.error("Error rendering dashboard:", err); + }); + }; render() { return ( diff --git a/images/dashboard.png b/images/dashboard.png index 8977605..cea010f 100644 Binary files a/images/dashboard.png and b/images/dashboard.png differ diff --git a/images/embedconfig-location.png b/images/embedconfig-location.png index f1c3240..acb216f 100644 Binary files a/images/embedconfig-location.png and b/images/embedconfig-location.png differ