Daml-ui-template

@bartcant Thank you. This is extremely helpful to me and I am studying this equipment finance application.
It is indeed a great learning material. I do love to see how daml could solve business problems.

Thank you very much for your advice.:slight_smile:

1 Like

It will take me some time to reproduce so I can make sure this is the issue, but at first glance it looks like there are two problems with this block: the location should be /v1/stream, not /v/stream, and it should have a line that sets the Connection header:

      proxy_set_header Connection "Upgrade";

From MDN:

Note : Connection: upgrade must be set whenever Upgrade is sent.

1 Like

@Gary_Verhaegen I have revised the nginx.conf as follows but the same errors occurred on my Chrome:

server {
listen 80;

location / {
     root   /usr/share/nginx/html;
     index index.html index.htm;
     try_files $uri $uri/ /index.html;
}
    location /v1 {
        proxy_pass http://json:7575;
      }
    location /v1/stream {
    proxy_pass http://json:7575; 
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Host $host;
    proxy_set_header Connection "Upgrade";
}
}

The React build for the example really drives me nut. Looking at the Chrome errors, the request URL is:

ws://localhost:7575/v1/stream/query

and the exercise URL is:

https://api/data/localhost/v1/create

Do these have anything to do with the nginx location blocks? I don’t know as I have no idea on the React codes.

Please advise me further on this and it seems I am close to the end of this learning exercise. Many Many Thanks.

1 Like

nginx.conf seems OK to me, but I’m a bit perplexed by the URLs you’re showing. Do you have any idea where those api/data/localhost are coming from? Also do you only have issues with streaming, or are exercises also failing?

Could you perhaps share your entire setup, including how to build the various Docker images involved?

1 Like

@Gary_Verhaegen Let me repeat what I have done.

First, I git cloned the daml-ui-template and renamed it as “my-app”. Then I followed the Readme and “make build” everything. Thereafter, I “daml start” the sandbox as well as “yarn start” the UI. The my-app is running on http://localhost:3000. So far, everything is fine and I can play around with it to create and exercise and the UI will update automatically.

Since the React was also built. I tried it with “serve -s build” and open my Chrome on http://localhost:5000. The UI login screen did show up and after login (as Alice or Bob), I can see the UI without any data. This was expected and I opened the Chrome developer tool to find the following two errors:

WebSocket connection to 'wss://api:5000/data/localhost:5000/v1/stream/query' failed:

useStreamQuery: WebSocket connection failed.

I was not surprised to read them as I am sure the UI was not connected to the sandbox or api server. The funny thing happened when I tried with http://172.20.10.5:5000 as prompted ( 172.20.10.5 is my mac IP). I got a completely blank screen. The Chrome developer tool showed the following errors:

DOMException: Failed to construct 'WebSocket': The URL 'wss://api.20.10.5:5000:5000/data/172/v1/stream/query' is invalid.

Uncaught DOMException: Failed to construct 'WebSocket': The URL 'wss://api.20.10.5:5000:5000/data/172/v1/stream/query' is invalid.

I did not know what was going on as I have no React knowledge to find out what went wrong.

Anyway, I followed your previous advice and build up a “my-daml” docker image:

FROM digitalasset/daml-sdk:1.7.0
USER root
RUN mkdir /app
COPY .daml/dist/ /app/
RUN chown -R daml:daml /app
USER daml

I run the image for two containers:

docker run --name sandbox my-daml daml sandbox --address 0.0.0.0 --ledgerid my-app /app/my-app-0.0.1.dar

docker run --name json --link sandbox my-daml daml json-api --ledger-host sandbox --ledger-port 6865 --http-port 7575 --address 0.0.0.0

I set up my dockerfile to build the image “my-nginx”:

FROM nginx:stable-alpine
COPY build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

with nginx.conf as:

server {
listen 80;

location / {
     root   /usr/share/nginx/html;
     index index.html index.htm;
     try_files $uri $uri/ /index.html;
}
    location /v1 {
        proxy_pass http://json:7575;
      }
    location /v1/stream {
    proxy_pass http://json:7575; 
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Host $host;
    proxy_set_header Connection "Upgrade";
}
}

I run this my-nginx image as:

docker run --name nginx --link json -p 80:80 my-nginx

I open the Chrome browser http://localhost and again see the UI without any data. The Chrome developer tool shows the two errors as:

WebSocket connection to 'wss://api/data/localhost/v1/stream/query' failed: 

useStreamQuery: WebSocket connection failed

I tried to create a new asset and got the error:

Uncaught (in promise) TypeError: Network request failed
    at XMLHttpRequest.E.s.onerror 

Another strange thing happened when I go to http://172.20.10.5 and the errors become:

WebSocket connection to 'wss://api.20.10.5/data/172/v1/stream/query' failed: 

useStreamQuery: WebSocket connection failed.

Please take note that the IP address was somehow changed.

The above are what I have tried in the last several weeks but still failed. I did try the create-daml-app template and I think its React build cannot be served either. Please advice.

Sorry to take so much of your time but I do really want to see how things are set up for production.

1 Like

I can reproduce this.

No idea what “serve” is or why you’d expect it to work here. I don’t have a serve binary on my machine. I’ll ignore this for now and skip to the Docker bits.

…

Ok, this took a bit a digging, but I found out the issue(s). The first one is with the template itself, which seems to assume it’s getting deployed to Daml Hub and nothing else. So no connection gets established because the JS code mangles the URL.

To get it to work, you’ll need to change the ui/src/config.tsx file to match your setup. Specifically:

diff --git a/ui/src/config.ts b/ui/src/config.ts
index c2421f5..5ab2971 100755
--- a/ui/src/config.ts
+++ b/ui/src/config.ts
@@ -5,12 +5,12 @@ export const isLocalDev = process.env.NODE_ENV === 'development';
 let host = window.location.host.split('.')
 
 const applicationId = 'daml-ui-template'
-export const ledgerId = isLocalDev ? applicationId : host[0];
+export const ledgerId = isLocalDev ? applicationId : 'my-app';
 
-let apiUrl = host.slice(1)
-apiUrl.unshift('api')
+//let apiUrl = host.slice(1)
+//apiUrl.unshift('api')
 
-export const httpBaseUrl = isLocalDev ? undefined : ('https://' + apiUrl.join('.') + (window.location.port ? ':' + window.location.port : '') + '/data/' + ledgerId + '/');
+export const httpBaseUrl = isLocalDev ? undefined : ('http://' + host.join('.') + (window.location.port ? ':' + window.location.port : '') /*+ '/data/' + ledgerId*/ + '/');
 
 // Unfortunately, the development server of `create-react-app` does not proxy
 // websockets properly. Thus, we need to bypass it and talk to the JSON API

Going over those changes:

  • On Daml Hub, the application ID corresponds to the (first segment of the) hostname; this is not true for you, so let’s just set the app name to the correct value ('my-app').
  • We don’t want to try and guess anything, really, from the hsotname, so we don’t need the apiUrl variable.
  • We’re not running on https here so we need to set the url to http, otherwise it will infer a (secure) wss connection, which the browser will not be able to establish. Switching to http ensures we get a ws connection instead, which will work.
  • We remove the /data/ledgerid bits from the URL.

Note that I’m expanding those changes so you can understand the issues and what’s happening here. A simple way to make it work is to just set httpBaseUrl to undefined, regardless of whether we’re in development or not.

You’ll need to rerun make build and rebuild your my-nginx Docker image after that change.

At this point the network connections will work, but there is still one problem: the JSON API is going to refuse any connection from nginx, because it is running in “production” mode and our current setup is insecure. The JSON API really wants you to use HTTPS in production between the browser and nginx (which is a good thing, but not quite an option here). You’ll need to add a flag to the json api startup command to tell it to accept HTTP connections: --allow-insecure-tokens.

With those two changes (config.tsx and --allow-insecure-tokens), you should be able to get everything working.

2 Likes

Just to clarify these lines, the ones that start with a single - need to be removed, and the ones that start with a single + need to be added. The - and + themselves should not be in the final file, they’re just indicators of what to add/remove.

Really each of these are just showing small changes to the original line (ex. host[0] to 'my-app'; or let to //let) but this is the way it’s represented in the diff format that Gary generated.

1 Like

Right. For the avoidance of doubt, here is a working version of config.ts in full:

import * as jwt from "jsonwebtoken";

export const isLocalDev = process.env.NODE_ENV === 'development';

let host = window.location.host.split('.')

const applicationId = 'daml-ui-template'
export const ledgerId = isLocalDev ? applicationId : 'my-app';

//let apiUrl = host.slice(1)
//apiUrl.unshift('api')

export const httpBaseUrl = undefined;

// Unfortunately, the development server of `create-react-app` does not proxy
// websockets properly. Thus, we need to bypass it and talk to the JSON API
// directly in development mode.
export const wsBaseUrl = isLocalDev ? 'ws://localhost:7575/' : undefined;

export const createToken = (party : string) => jwt.sign({ "https://daml.com/ledger-api": { ledgerId, applicationId, admin: true, actAs: [party], readAs: [party] } }, "secret")

let loginUrl = host.slice(1)
loginUrl.unshift('login')

export const dablLoginUrl = loginUrl.join('.') + (window.location.port ? ':' + window.location.port : '') + '/auth/login?ledgerId=' + ledgerId;

export const damlPartyKey = applicationId + ".daml.party";
export const damlTokenKey = applicationId + ".daml.token";
2 Likes

Getting an app to work both locally and on projectdable with user friendly names at the same time was an initial pain point for us as well …

@gyorgybalazsi solved this by modifying the ui-template and implemented an approach where we can comment/uncomment a portion of our code in src/useKnownParties.tsx to

1 . get the known parties that are setup under projectdable
or
2. use a set of parties that are setup during a start-up script for the local version of the app

// Usage in components:
// import {useKnownParties} from '../UseKnownParties'
// Within the component:
// const {displayName, partyIdentifier} = useKnownParties () 

// YOU NEED THESE IMPORTS FOR RUNNING LOCALLY - START
import React, { useEffect } from 'react';
import { useLedger, useParty } from '@daml/react';
import Ledger, { PartyInfo } from '@daml/ledger';
// YOU NEED THESE IMPORTS FOR RUNNING LOCALLY - END

// YOU NEED THIS IMPORT FOR RUNNING ON DAML HB - START
/* import { PartyInfo } from '@daml/ledger';  */
// YOU NEED THIS IMPORT FOR RUNNING ON DAML HB - END

export function useKnownParties () {
    // YOU NEED THIS PART FOR RUNNING LOCALLY - START
     const [knownParties, setKnownParties] = React.useState<PartyInfo[]>([]);
    const ledger: Ledger = useLedger();
    useEffect(() => {
    const getKnownParties = async () => {
        let lst = await ledger.listKnownParties();
        setKnownParties(lst);
    } ;
    getKnownParties()
    }, [ledger]); 
    // YOU NEED THIS PART FOR RUNNING LOCALLY - END

    // YOU NEED THIS PART FOR RUNNING ON DAML HUB - START
    /*  const knownParties : PartyInfo[] = require('./parties.json')  */
    // YOU NEED THIS PART FOR RUNNING ON DAML HUB - END

    return {
        displayName : (id: string ): string => {
            return knownParties.filter((x : PartyInfo) => x.identifier === id)[0]?.displayName || id 
            },
        partyIdentifier : (displayName: string): string => {
            return knownParties.filter((x : PartyInfo) => x.displayName === displayName)[0]?.identifier || displayName 
            },
        knownPartyDisplayNames: knownParties.map((x : PartyInfo)  => x.displayName || x.identifier)
    }
}

full source code at
https://github.com/RethinkLedgers/ef-smartcontract

2 Likes

@Gary_Verhaegen After revising the config.ts and the json api, I can now get everything working. This is extremely useful for me to see how the UI can connect to the daml ledger and I can now proceed to explore further.

BTW, I find it very intuitive to see how you use the sdk docker image. I will also try to run triggers and scripts with the image.

I have to thank both of you @Gary_Verhaegen @anthony again for the detailed explanations and advices. A billion thanks and all the best to you all there.

3 Likes

It’s really great to see that it all worked out @EllisAllison!

You guys rock @Gary_Verhaegen @anthony! Always there to support forum members :pray:

3 Likes

You’re very welcome but I think Gary deserves most of the thanks :slight_smile: