Periodically changing token with the React components

We want to periodically go to the IAM server, get a token and use that with DamlLedger (the React Functional Component).

Is it possible to “reread” the token or somehow notice it changed and use the new one with DamlLedger?
There is this “useReload” function here. Is it a possible solution to call this reload function if the token changes? Why can’t the DamlLedger automatically pick up changes (change of token )?

2 Likes

I think useReload/reload is not for this (but for reloading queries and data).

How then can one modify the token used by DamlLedger (making it reread the token)?

1 Like

After examining the code I think that maybe a “getter” returned by useState can be passed in DamlLedger as token. When the value of that getter changes - e.g. the corresponding setter is called - DamlLedger should pick up the new token. Is this true?

My computer is struggling a bit at the moment so I can’t test this myself, but I believe you should be able to refresh the credentials like any other React props.

Concretely, if you start from the create-daml-app template, you’ll see in App.tsx that the credentials object is created as a React piece of state, and there is a setCredentials method to change it. The Credentials object contains the token and the party.

How you organise your code for this to work will depend on the rest of your code, but it should not be too hard to make sure the setCredentials function is passed through to whatever piece of code you have to refresh your token.

For example, based on index.tsx in create-daml-app, something like:

import React from 'react';
import ReactDOM from 'react-dom';
import 'semantic-ui-css/semantic.min.css';
import './index.css';
import App from './components/App';
import Credentials from './Credentials';

const [credentials, setCredentials] = React.useState<Credentials | undefined>();

ReactDOM.render(<App creds={credentials} setCreds={setCredentials} />, document.getElementById('root'));

setTimeout(TOKEN_REFRESH_INTERVAL, () => {
  const newToken = getNewToken();
  setCredentials((c) => ({...c, token = newToken});
});

and App.tsx would need to be changed to receive the credentials rather than creating it itself, something like:

import React from 'react';
import LoginScreen from './LoginScreen';
import MainScreen from './MainScreen';
import DamlLedger from '@daml/react';
import Credentials from '../Credentials';
import { httpBaseUrl } from '../config';

/**
 * React component for the entry point into the application.
 */
// APP_BEGIN
const App: React.FC = ({creds, getCreds}) => {

  return creds
    ? <DamlLedger
        token={creds.token}
        party={creds.party}
        httpBaseUrl={httpBaseUrl}
      >
        <MainScreen onLogout={() => setCreds(undefined)}/>
      </DamlLedger>
    : <LoginScreen onLogin={setCreds} />
}
// APP_END

export default App;

Again, I’m not in a position to test this myself, so apologies if this sends you down the wrong path, but I believe this should work based on the code in createLedgerContext.

3 Likes

Hi @gaborh,

Now that my computer is back in working state, I’ve been able to test this out. The above was the right general idea, but I had forgotten about React hook rules. The simplest way to get what I believe you want from the create-daml-app template is to turn the credentials into a prop of the root component. This can be done with this full diff from a blank daml new t --template=create-daml-app:

diff --git a/ui/src/components/App.tsx b/ui/src/components/App.tsx
index 1f4a107..2cc6d6a 100644
--- a/ui/src/components/App.tsx
+++ b/ui/src/components/App.tsx
@@ -12,18 +12,16 @@ import { httpBaseUrl } from '../config';
  * React component for the entry point into the application.
  */
 // APP_BEGIN
-const App: React.FC = () => {
-  const [credentials, setCredentials] = React.useState<Credentials | undefined>();
-
-  return credentials
+const App: React.FC<{creds: Credentials | undefined, setCreds: (c: Credentials | undefined) => void}> = ({creds, setCreds}) => {
+  return creds
     ? <DamlLedger
-        token={credentials.token}
-        party={credentials.party}
+        token={creds.token}
+        party={creds.party}
         httpBaseUrl={httpBaseUrl}
       >
-        <MainScreen onLogout={() => setCredentials(undefined)}/>
+        <MainScreen onLogout={() => setCreds(undefined)}/>
       </DamlLedger>
-    : <LoginScreen onLogin={setCredentials} />
+    : <LoginScreen onLogin={setCreds} />
 }
 // APP_END
 
diff --git a/ui/src/index.tsx b/ui/src/index.tsx
index 328a4af..590f68a 100644
--- a/ui/src/index.tsx
+++ b/ui/src/index.tsx
@@ -6,5 +6,25 @@ import ReactDOM from 'react-dom';
 import 'semantic-ui-css/semantic.min.css';
 import './index.css';
 import App from './components/App';
+import Credentials from './Credentials';
 
-ReactDOM.render(<App />, document.getElementById('root'));
+function render(c: Credentials | undefined): void {
+  console.log(c);
+  ReactDOM.render(<App creds={c} setCreds={render}/>, document.getElementById('root'));
+}
+
+render(undefined);
+
+var alice = {party: "alice",
+             token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwczovL2RhbWwuY29tL2xlZGdlci1hcGkiOnsibGVkZ2VySWQiOiJ0LXNhbmRib3giLCJhcHBsaWNhdGlvbklkIjoidCIsImFjdEFzIjpbImFsaWNlIl19fQ.y7x_iLwmFSn9m3RdWSBQWndjxI0zttWhc8ESNWRNymM",
+             ledgerId: "t-sandbox"}
+
+var bob = {party: "bob",
+           token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwczovL2RhbWwuY29tL2xlZGdlci1hcGkiOnsibGVkZ2VySWQiOiJ0LXNhbmRib3giLCJhcHBsaWNhdGlvbklkIjoidCIsImFjdEFzIjpbImJvYiJdfX0.pr7jtKhM8rHJkfT8LhYtxrDXKhX1IzAj615Y1RbbyXk",
+           ledgerId: "t-sandbox"}
+
+function login_as(c1: Credentials, c2: Credentials): void {
+  setTimeout(() => { render(c1); login_as(c2, c1); }, 10 * 1000);
+}
+
+login_as(alice, bob);

With credentials being received as a prop, calls to ReactDOM.render will refresh the relevant components on every change of the credentials field. This demonstrates it with a silly ten-second rotation between Alice and Bob, but the same principle would of course work regardless of the source of change for the credentials.

The big caveat here is that since the credentials are no longer managed entirely from the UI, it falls on the programmer’s discipline to make sure logout really does mean logout, and not “logout until the callback that gets my refreshed token resets my credentials and logs me back in”.

3 Likes