nash-io / api-client-typescript

Official TypeScript client for interacting with the Nash Exchange.
MIT License
14 stars 3 forks source link

CORS issue #15

Open marcperrinp opened 4 years ago

marcperrinp commented 4 years ago

Trying to make an API request from the browser, but I'm getting a CORS error:

Access to fetch at 'https://app.sandbox.nash.io/api/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

I guess I need to fix this on my side*, but could you just confirm this is the right way to include a header at client login:

const client = new Client(EnvironmentConfiguration.sandbox, {
        headers: {
          "User-Agent": `nashcasino.com`
        }
      })

* : Not sure if there's any way to make this work locally since I'm in HTTP. Probably will just need to add local certificates to be in HTTPS, but will it be enough?

Any tips would be appreciated :)

marcperrinp commented 4 years ago

Suppose the HTTP vs HTTPS issue was out of the way, unless I'm mistaken I don't see a Access-Control-Allow-Origin: * in the API response, so how can I make API requests from the frontend? I'm afraid the answer is "I can't", no? 😛

jankjr commented 4 years ago

@marcperrinp it is possible. But you just need to set up a http proxy and direct it at the environment you want to interact with.

marcperrinp commented 4 years ago

@jankjr I tried in production, which runs on HTTPS of course, and I'm getting the same error..

Are you saying there is a way to bypass the fact that Nash API's response doesn't contain a Access-Control-Allow-Origin: * header?

jankjr commented 4 years ago

@marcperrinp

I think our CORS headers are restrictive for security reasons. So the browser probably won't let you call our service directly.

But what you can do is run an a small express server to act as a proxy for your browser app, which will remove the CORS restriction as you are calling your own service. It is essentially how our development setup works. Below is an example of now it can be configured for our production environment.

const app = express();
const proxy = require('http-proxy-middleware')

const PROXY_BACKEND = 'https://app.nash.io'

const proxyTimeout = 3600000
const cookieDomainRewrite = { '*': '' }
const onProxyRes = (proxyRes, req, res) => {
  const sc = proxyRes.headers['set-cookie']
  if (Array.isArray(sc)) {
    proxyRes.headers['set-cookie'] = sc.map(sc => {
      return sc
        .split(';')
        .filter(v => v.trim().toLowerCase() !== 'secure')
        .join('; ')
    })
  }
}

app.use(
    proxy('/api/socket', {
      target: PROXY_BACKEND,
      changeOrigin: true,
      ws: true,
      proxyTimeout,
      cookieDomainRewrite,
      onProxyRes,
    })
)

app.use(
    proxy('/api', {
      target: PROXY_BACKEND,
      changeOrigin: true,
      cookieDomainRewrite,
      onProxyRes,
    })
)
app.listen(3000);

If your proxy is hosted at localhost:3000 then you will need to initialize the SDK like so:

const nash = new Client({
      ...EnvironmentConfiguration.production,
      host: 'localhost:3000',
      isLocal: true
})

Once you deploy the service somewhere, say nashcasino.com/proxy, the SDK needs to be configured as follows:

const nash = new Client({
      ...EnvironmentConfiguration.production,
      host: 'nashcasino.com/proxy'
})
marcperrinp commented 4 years ago

Thanks a lot @jankjr.

Unfortunately I cannot set up a custom express app, as I'm using Vercel which only provides serverless functions. I did however successfully set up a rewrite rule specifically for API calls directed towards Nash (thanks to your explanation).

It works since I don't have the CORS issue anymore, but somehow it looks like I'm not getting a response. After some time, it appears I'm getting an "Invalid timestamp" error, but it may be due to some sort of timeout?

Anyway I just tried now (in production) at around 7:23:50 UTC if you have logs and time to check it out.

I'll continue to investigate, thanks again.

jankjr commented 4 years ago

@marcperrinp I think you can run a proxy in a serverless setup as well. But of course my snippet can't be used without some modifications then :)

Have you updated to the very latest SDK? It is the only thing I can think of that is causing the issue.

marcperrinp commented 4 years ago

@jankjr Upgrading to 5.0.35 (from 5.0.31) solved it!... for a minute only 🙀 It was working on my staging (which is connected to Nash's Production), but when I released to Production shortly after it wasn't anymore. Now back on my staging , it's not working anymore.

Those were the successful trades, so it was at around 11:30PM (UTC+2 time):

image

Did anything change on Nash side?

jankjr commented 4 years ago

Not sure @marcperrinp

Can you double check that the time is set correctly on your computer? I've seen that sometime the browser is reporting incorrect time.

marcperrinp commented 4 years ago

@jankjr Your answer helped me identify the issue. It is two-fold:

image

On the next spins, the chunks don't need to be fetched again so passing the order via the API goes smooth. When I was executing the order from the server, this didn't happen obviously, which is why I'm only having the issue now.

To solve this, I guess I'll have to dig into Webpack to understand how to have it pre-download those chunks.

Thanks for reading, I'm explaining in details hoping it could help others in the future 😄

marcperrinp commented 4 years ago

Hi @jankjr, I'm writing to you again after having tried many things, but I couldn't get it to work.

I narrowed it down to the fact that those 3 chunks are downloaded precisely when calling placeLimitOrder (other authenticated method of the API have the same behavior).

Do you have any idea what they could be?

jankjr commented 4 years ago

@marcperrinp

I know I suggested this before in a different context, I am wondering if you could retry the call if it fails?

try {
    // try placing the call first time
    return client.placeLimitOrder(...)
} catch(e){
    // if it fails because of timing issues because it is loading modules. Then calling it again should succeed?
    return client.placeLimitOrder(...)
        /// if it fails here again then maybe there is something else that is wrong
}

I am not sure I know how to coerce Webpack into behaving.

marcperrinp commented 4 years ago

@jankjr Thanks for the reply!

I tried it but it does not work.

What's happening actually is that the placeLimitOrder method calls the following mutation:

query: "mutation dhFillRPool($blockchain: Blockchain!, $dhPublics: [Base16]!) {↵  dhFillPool(dhPublics: $dhPublics, blockchain: $blockchain)↵}↵"

after 30+ seconds a response is returned (30s feels like it might have timed out, but weirdly I do get a response..):

image

then only is the actual placeLimitOrder mutation called:

image

and unsurprisingly, the return is an "Invalid timestamp" error:

image

As a result the try // catch you suggested doesn't actually catch an error until 30 seconds after the spin started. Not only is this not acceptable in terms of playing experience, but the fallback placeLimitOrder method in the catch statement doesn't seem to trigger anyway.

My questions are the following:

Thanks again for your time, Jan!

Marcpepe commented 4 years ago

This is partly solved thanks to one of @jankjr's PRs.

Adding this snippet to your code will prevent the delay/timeout:

import {
  configurePoolSettings
} from '@neon-exchange/nash-protocol'

// Do not go lower than 4. The lower, the more work is done during the trade. (Default is 100)
configurePoolSettings(4)

I'm keeping this thread open because I'm still having a freeze issue. It's due to the browser doing some calculations which throttles the RAM, apparently. I'll be this thread updated with any additional info.