XRPL-Labs/Developer-Help-Center

More explanation required

Closed this issue ยท 14 comments

Here you go :)
fc033ac

Thanks, followup question, how to check if current xumm.user context has full access or 'read only'?

That isn't a user property as it can't be normalized;

The user property contains the data always available regardless of OAuth2, Web3 or xApp method.

The Full Access / Read Only property is not available in Web2/OAuth2 context, as a user must have Full Access otherwise they couldn't sign the Sign Request.

Meaning this property is xApp only, and thus available in the xApp user context instead of the normalized user object:

Xumm.environment.ott contains the full xApp OTT object, including the account access level.

Xumm.environment.ott is empty. When inspecting via remotejs.

const xumm = new Xumm('xxxxxxx-yyyyyyy-zzzzzz...');
xumm.on("ready", async () => {
  if(xumm.runtime.xapp) {
        console.log(xumm.environment); //log: {"jwt":{},"ott":{},"openid":{},"bearer":{},"ready":{},"success":{},"retrieved":{},"retrieving":{}}
  }
});

Second thing I tried is using <script src="https://cdn.jsdelivr.net/npm/xumm-sdk/dist/browser.min.js"></script>, which works 'out of the box'.

 const {XummSdkJwt} = require('xumm-sdk')
    const Sdk = new XummSdkJwt('xxxxxxx-yyyyyyy-zzzzzz...');
    
    Sdk.getOttData().then(c => {
        console.log('OTT Data', c)
    })

I'm thinking that I'm missing some uncalled promise?

The OTT is is a promise on the individual property, and thus should be awaited and not referred to as xumm.environment.

So to take your example:

const xumm = new Xumm('xxxxxxx-yyyyyyy-zzzzzz...');
xumm.on("ready", async () => {
  if(xumm.runtime.xapp) {
    xumm.environment.ott.then(ott => {
      console.log(ott)
    })
  }
});

Will check back later with this, I think promise wasn't executing, no error in console. I think its worth exploring this further as it may indicate bug in SDK or lack of error feedback.

The individual OTT object is async, meaning console logging the parent object (xumm.environment) or even directly console logging xumm.environment.ott won't work: the individual xumm.environment.ott promise needs to be awaited :)

That's works in all our xApps :) Looking forward to hearing your results.

Alright, it works in xumm through xApp, doesn't work in local browser. You should document this somewhere, since this behavior changed in v1.0 in comparison to old method via XummSdkJwt.

Edit: Since 'universal' script does not allow executing promises in browser, this slows down development of xApps.

What do you mean with "doesn't allow executing promises in browser"?

The universal one is exactly that: universal. So it works exactly the same way, with promises, in the browser.

But as mentioned before, as the browser version doesn't use an OTT, the OTT data will indeed be empty :)

The Universal lib. is simply a wrapper around browser/xApp/CLI, to offer a uniform way to work with all of them: through individual promises.

U hear you, but, lets take this example, run in web browser.

What I replaced is: <script src="https://xumm.app/assets/cdn/xumm.min.js"></script>

See pong promise - state: pending
See no EUR rates has been resolved: xumm.helpers.getRates('EUR').then(r => console.log('rates', r))

Love to hear your input on this, it is not excluded that I misunderstood something.

Thanks!

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xumm Demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://xumm.app/assets/cdn/xumm.min.js"></script>
  </head>
  <body>
    <div class="container mt-2 px-4">
      <h1>Hello, world!</h1>

      <div>State: <span id="state">...</span></div>

      <h1>
        <a href="/sample/index.html">Reload</a>
      </h1>

      <button id="payload">Payload</button>

      <h2 id="xapp" style="display: none;" class="mt-4">
        <code class="h3" id="account">...</code>
        <button id="dpick">Destination Picker</button>
        <button id="qr">QR scanner</button>
      </h2>

      <div id="pkce" style="display: none;">
        <h2>PKCE (Browser)</h2>
        <code class="h3" id="sub">... (Please sign in)</code>
        <button id="auth">Auth</button>
        <button id="logout">Logout</button>
      </div>

    </div>

    <script type="module">
      //import '../dist/browser.js'

      let instance = 0

      const run = async () => {
        instance++
        const _instance = String(instance)

        // const xumm = new Xumm('some-api-key')
        const xumm = new Xumm('8525e32b-1bd0-4839-af2f-f794874a80b0')
        // const xumm = await new Xumm('somejwt')
        // For debuggig purposes, so we can access the main class in the browser console
        window.xumm = xumm

        xumm.helpers.getRates('EUR').then(r => console.log('rates', r))

        xumm.environment.jwt?.then(r => console.log('jwt', r))
        xumm.environment.bearer?.then(r => console.log('bearer', r))
        xumm.environment.openid?.then(r => console.log('openid', r))
        xumm.environment.ott?.then(r => console.log('ott', r))

        // xumm.payload?.create({TransactionType: 'SignIn'}).then(r => console.log('payload', r))
        // xumm.helpers.getCuratedAssets().then(r => console.log('xumm.helpers.getCuratedAssets', r))
        // xumm.helpers.getKycStatus().then(r => console.log('xumm.helpers.getKycStatus', r))
        // xumm.helpers.getTransaction().then(r => console.log('xumm.helpers.getTransaction', r))
        // xumm.helpers.verifyUserTokens().then(r => console.log('xumm.helpers.verifyUserTokens', r))
        // xumm.helpers.getRates().then(r => console.log('xumm.helpers.getRates', r))

        // Expired JWT to test with:
        // const xumm = await new Xumm('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiI0N2ZkZmNiNy04MzYyLTQ3ZmUtOWE4OS1hMmVmYzU1ZTg2ZDUiLCJzdGF0ZSI6ImU1MWNlOGEwMDc0NjNiYjFiMzY5ZWNmYTZkOWI2MmZkZDBiNmEwYmMxN2UyODlhNDQxNDkzMGMzNzIxZGM0MjgzZTYwYjUzZDdiMmM1YjhlMWM3YWYxOGMzMDQ5ZGUwMzEyZDRkZjY3YzgwMGRlZmE1ZDNlOThhNWQxNGY0MDgxIiwic2NvcGUiOiJYdW1tUGtjZSIsImF1ZCI6IjQ3ZmRmY2I3LTgzNjItNDdmZS05YTg5LWEyZWZjNTVlODZkNSIsInN1YiI6InJ3aWV0c2V2TEZnOFhTbUczYkVaekZlaW4xZzhSQnFXRFoiLCJlbWFpbCI6IjQ3ZmRmY2I3LTgzNjItNDdmZS05YTg5LWEyZWZjNTVlODZkNStyd2lldHNldkxGZzhYU21HM2JFWnpGZWluMWc4UkJxV0RaQHh1bW0ubWUiLCJhcHBfdXVpZHY0IjoiNDdmZGZjYjctODM2Mi00N2ZlLTlhODktYTJlZmM1NWU4NmQ1IiwiYXBwX25hbWUiOiJTb21lIFN1cGVyIENvb2wgQXBwIiwicGF5bG9hZF91dWlkdjQiOiIwNDM0NjkxNS0wYzEyLTRiZDQtOTczMS0xMWRhZDQ2ZDE2ODEiLCJ1c2VydG9rZW5fdXVpZHY0IjoiZDhiYjQ0NGUtYTk2Yi00OTc3LTkyNjUtNWJlYzE3NDkwMjlkIiwibmV0d29ya190eXBlIjoiTUFJTk5FVCIsIm5ldHdvcmtfZW5kcG9pbnQiOiJ3c3M6Ly94cnBsY2x1c3Rlci5jb20iLCJpYXQiOjE2Njk1OTI4ODIsImV4cCI6MTY2OTY3OTI4MiwiaXNzIjoiaHR0cHM6Ly9vYXV0aDIueHVtbS5hcHAifQ.SUCoM5Y3R2GyeMC9KLqj8RuhlYUPqhpczdEwAnIOgYhPKaxfUDGU-yYCiX5JB5qWC6zveg2fJ_u1smG1Eh6k7jBqt-WNsf-kUayzwlOmZjj7DSSEJG8itEWFCnjUeFVWU9hyZZSCf_wO-4C_tT0-eWoK4VLhmHJrXOoTyH9xpCLzGDpk-rLxRDNyxQwbjSJCAU0cQfipAMzrjOddqdUgqjyNyxysy1DZrNNfppuWC9LfZaTfauQdZPZeaXAeMeKJmdQ_LrSI-UCO_CzWFyWeSkgN61NOCyz7Cc8S023P99DKaf9ofLD99FcTtVy-RYr3uOn3Kt_JEhDhaZJfZT4I6EWMq0qNfAlHlIZiQBI4Jq7ScQB4my25dIY4-vc4Prmjk_LAkW-fTC2wjleLDbqi27LJpeMkxR_evGUWJamodz8l4uhNTB_CrgceHikKW0IBPOSWrCnJsAjjRgq_73fg6dJ06KgfQoKU25sxnyShtak4e7VaBBM92MEmmkHugpJY')

        document.getElementById('payload').onclick = () => {
          console.log('payload')

          xumm.payload.createAndSubscribe({
            txjson: {
              TransactionType: 'Payment',
              Destination: 'rfHn6cB5mmqZ6fHZ4fdemCDSxqLTijgMwo',
              Amount: String(1) // Drops
            }
          }, eventMessage => {
            if (Object.keys(eventMessage.data).indexOf('opened') > -1) {
              // Update the UI? The payload was opened.
            }
            if (Object.keys(eventMessage.data).indexOf('signed') > -1) {
              // The `signed` property is present, true (signed) / false (rejected)
              return eventMessage
            }
          })
          .then(({ created, resolved }) => {
            console.log('Payload URL:', created.next.always)
            console.log('Payload QR:', created.refs.qr_png)

            return resolved
          })
          .then(d => console.log('Payload resolved', d))
        }

        if (xumm.runtime.xapp) {
          document.getElementById('xapp').style.display = 'block'
          document.getElementById('dpick').onclick = () => xumm.xapp.selectDestination()
          document.getElementById('qr').onclick = () => xumm.xapp.scanQr()
          // document.getElementById('qr').onclick = () => xumm.xapp.openBrowser({ url: 'https://tweakers.net' })

          xumm.user.account.then(account => document.getElementById('account').innerText = account)

          xumm.xapp.on('destination', data => {
            console.log('A-xapp-destination (event) @' + _instance, data.destination?.name, data.destination?.address, data?.reason)
          })

          xumm.on('destination', data => {
            console.log('B-xapp-destination (event) @' + _instance, data.destination?.name, data.destination?.address, data?.reason)
          })

          console.log('a-flow')
        }

        if (xumm.runtime.browser && !xumm.runtime.xapp) {
          xumm.user.account.then(account => document.getElementById('sub').innerText = account)

          document.getElementById('pkce').style.display = 'block'

          document.getElementById('auth').onclick = () => {
            xumm.authorize().catch(e => console.log('e', e))
          }

          document.getElementById('logout').onclick = () => {
            xumm.logout()
            document.getElementById('sub').innerText = '... (Please sign in)'
          }

          xumm.on("error", (error) => {
            console.log("error (event)", error)
          })

          xumm.on("logout", () => {
            console.log("logout (event)", logout)
          })

          xumm.on("success", async () => {
            console.log('success (event)', xumm.user.account.then(a => console.log(a)))
          })

          xumm.on("retrieved", async () => {
            console.log("Retrieved: from localStorage or mobile browser redirect (event)", xumm.user.account.then(a => console.log(a)))
            // document.getElementById('sub').innerText = await xumm.user.account
          })

          console.log('b-flow')
        }

        console.log(xumm.runtime)

        xumm.on("retrieving", () => console.log("retrieving (event)"))
        xumm.on("ready", () => console.log("ready (event)"))

        xumm.environment.retrieving?.then(r => {
          document.getElementById('state').innerText = 'retrieving'
          console.log('retrieving (promise)', r)
        })
        xumm.environment.ready?.then(r => {
          document.getElementById('state').innerText = 'ready'
          console.log('ready (promise)', r)
        })

        // console.log('xumm.user.account', await xumm.user.account)
        // console.log('xumm.user.picture', await xumm.user.picture)
        // console.log('xumm.user.name', await xumm.user.name)
        // console.log('xumm.user.domain', await xumm.user.domain)
        // console.log('xumm.user.source', await xumm.user.source)
        // console.log('xumm.user.networkType', await xumm.user.networkType)
        // console.log('xumm.user.networkEndpoint', await xumm.user.networkEndpoint)
        // console.log('xumm.user.blocked', await xumm.user.blocked)
        // console.log('xumm.user.kycApproved', await xumm.user.kycApproved)
        // console.log('xumm.user.proSubscription', await xumm.user.proSubscription)

        console.log('pong@' + _instance, xumm.ping().then(pong => console.log(pong)))

        // setTimeout(async () => {
        //   console.log('xumm.xapp', await xumm.xapp)
        //   console.log('xumm.push', await xumm.push)
        // }, 1000)
      }

      run();

      // setTimeout(function () {
      //   console.log('Run again')
      //   run();
      // }, 5000)
    </script>
  </body>
</html>

image

You console log the entire Promise, not the result of the Promise.

So:

console.log(xumm.ping().then(pong => console.log(pong)))

This returns the actual Promise directly, before the promise resolved.

If you want the result of the promise, you should execute it in the callback, or use async/await;

xumm.ping().then(pong => console.log(pong))
// Or:
xumm.ping().then(pong => {
  console.log('This is where the promise is resolved', pong)
})

^^ This will yield the pong, as it is the callback.

Alternatively (if you are in a 'JS module'):

const pong = await xumm.ping()
console.log('Now we awaited and we got a response', pong)

Same async pattern applies on rates etc.

I tried to make it clear now. Try it yourself in web browser. No pong, no rates.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xumm Demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://xumm.app/assets/cdn/xumm.min.js"></script>
  </head>
  <body>
    <div id="start">...</div>
    <div id="ping">...</div>
    <div id="showrates">Loading rates...</div>
    <div id="end">...</div>
    <script>
      var run = async () => {
        const xumm = new Xumm('528f687b-c06a-44a9-b11f-996ec0897011')

        console.log('run start');
        document.getElementById('start').innerText = 'start executed.'

        xumm.ping().then(pong => {
          document.getElementById('showrates').innerText = 'Pong.'
        });

        //following await stalls script
        await xumm.helpers.getRates('EUR').then(r => {
          document.getElementById('showrates').innerText = 'Rates loaded.'
        });

        console.log('run end');
        document.getElementById('end').innerText = 'end executed.'
      };
      run();
    </script>
  </body>
</html>

In the example above you're not getting any rates because the session has not been signed in yet.

There are three ways to authorize the Xumm SDK:

  1. xApp: the SDK authorizes itself as there's an OTT
  2. Backend: provide API Key + Secret, instant authorized
  3. Frontend: provide API Key, and have the end user sign in. Only when a sign in has happened, a JWT is handed out which is used for authorization. Until then, all promises stay pending, as the authorization (session) is not complete.

Working example:
https://duprtep.dlvr.cloud/index.html (see source)

What you will notice is that the rates pop in right after the user signed in. The promise stays pending till all requirements (valid session) are satisfied and then proceeds as expected.

All clear. Thank you.