Resetting cookies on new session

Bismuth_doug
Tourist
8 0 7

Hi!

 

I've built an app with a koa server/routes, building upon the following tutorial: https://developers.shopify.com/tutorials/build-a-shopify-app-with-node-and-react/set-up-your-app,

 

I'm wondering if it is possible to reset the shopOrigin and accessToken cookies every time the app is loaded, instead of only when the app is installed for the first time.

 

I'm running into a problem wherein if I load the app in one store, and load the app in a different store in the same browser, wires get crossed and one store loads the data from another store, because of my koa routes use these cookies, to determine the target url of the http requests.

 

Can anybody recommend a way to reset these cookies every time the app loads, or otherwise to restructure my code so these routes don't depend on cookies?  Is it possible, for instance, to access a shoporigin and accesstoken on the client side, through polaris?

 

Attaching my server.js file with my auth and routes, and the react file in which im using these routes to make HTTP requests.

 

 

server.js

require('isomorphic-fetch');
const dotenv = require('dotenv');
const Koa = require('koa');
const next = require('next');
const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth');
const { verifyRequest } = require('@shopify/koa-shopify-auth');
const session = require('koa-session');

const Router = require('koa-router');
const router = new Router();

dotenv.config();

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

// firebase

var admin = require("firebase-admin");

var serviceAccount = require("./serviceAccountKey.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://marquee-by-bismuth-2eca6.firebaseio.com"
});

const db = admin.firestore();

//


const { SHOPIFY_API_SECRET_KEY, SHOPIFY_API_KEY} = process.env;

app.prepare().then(() => {
  const server = new Koa();
  server.use(session(server));
  server.keys = [SHOPIFY_API_SECRET_KEY];

  //Auth for app
  server.use(
    createShopifyAuth({
      apiKey: SHOPIFY_API_KEY,
      secret: SHOPIFY_API_SECRET_KEY,
      scopes: ['read_themes', 'write_themes'],
      afterAuth(ctx) {
        const { shop, accessToken } = ctx.session;
          ctx.cookies.set('shopOrigin', shop, { httpOnly: false });
          ctx.cookies.set('accessToken', accessToken );
          console.log(shop)
        ctx.redirect('/');
      },
    }),
  ).use(verifyRequest());

  //PUT route for creating liquid file
  router.put('/api/:object', async (ctx) => {
    const marquee_content = require("./marquee-content.js").content
    const body = JSON.stringify({ asset: {key: "sections/marquee.liquid", value: marquee_content} })
    const url = `https://${ctx.cookies.get('shopOrigin')}/admin/api/2019-07/themes/${ctx.params.object}/assets.json`
    try {
      const results = await fetch( url, {
        method: 'PUT',
        body: body,
        headers: {
          "X-Shopify-Access-Token": ctx.cookies.get('accessToken'),
          'Content-Type': 'application/json',
        },
      }
    )
      .then(response => response.json())
      .then(json => {
        return json;
      }).then( () => {
        const store = ctx.cookies.get('shopOrigin').split('.')[0]
        docRef = db.collection("stores").doc(store)
        docRef.get().then(function(doc){
            if (doc.exists) {
              let newInstall = {
                date: Date.now(),
                themeID: ctx.params.object
              }
              let oldInstalls = doc.data().installs
              const data = {
                store: store,
                installs: [...oldInstalls, newInstall]
                }
              return db.collection('stores').doc(store).set(data).then(() => {
                console.log("written to database")
              })
            } else {
              const data = {
                store: store,
                installs: [{
                    date: Date.now(),
                    themeID: ctx.params.object
                  }]
                }
              return db.collection('stores').doc(store).set(data).then(() => {
                console.log("written to database")
                })
              }
          }
      );
    })
      ctx.body = {
        status: 'success',
        data: results
      };
    } catch (err) {
      console.log(err)
    }
  })

  //Get route to access list of themes
  router.get('/themes', async (ctx) => {
    const url = `https://${ctx.cookies.get('shopOrigin')}/admin/api/2019-07/themes.json`
    try {
      const results = await fetch( url, {
        method: 'GET',
        headers: {
          "X-Shopify-Access-Token": ctx.cookies.get('accessToken'),
          'Content-Type': 'application/json',
        },
      })
      .then(response => response.json())
      .then(json => {
        return json;
      });
      ctx.body = {
        status: 'success',
        data: results
      };
    } catch (err) {
      console.log(err)
    }
  })

  server.use(router.routes());
  server.use(router.allowedMethods());

  server.use(async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
    return
  });

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

 

React Component:

 

import { EmptyState, Spinner, Layout, Page, Select, Button } from '@shopify/polaris';
import Cookies from 'js-cookie';

class Selector extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      selecting: true,
      loading: true,
      selected: '',
      themes: [],
      redirect: false
    };
  }

  componentDidMount(){
    this.getThemes();
  }

  render() {
    return (
        <Layout.Section>
          {this.renderSelector()}
          {this.renderRedirect()}
          {this.renderSpinner()}
        </Layout.Section>
    );
  };


  getThemes = async () => {
    fetch("/themes", { method: "GET"})
    .then(response => response.json())
    .then(json => this.setState({themes: json.data.themes, loading: false}))
  };

  handleChange = (newValue) => {
    this.setState({selected: newValue});
  };

  renderRedirect = () => {
    if (this.state.redirect && !this.state.loading) {
      return (
        <Layout.Section>
          <a
            target="_blank"
            style={{textDecoration: 'none'}}
            href={'http://' + Cookies.get('shopOrigin') + `/admin/themes/${this.state.selected}/editor`}>
              <Button primary>OPEN CUSTOMIZER</Button>
          </a>

          <Button primary onClick={this.triggerReset}>ADD MARQUEE TO ANOTHER THEME</Button>
        </Layout.Section>
      )
    }
  }

  renderSpinner = () => {
    if(this.state.loading){
      return (
        <EmptyState>
          <Spinner/>
        </EmptyState>
      )
    }
  }

  triggerReset = () => {
    this.setState({
      selecting: true,
      selected: '',
      redirect: false
    })
  }

  renderSelector = () => {
    if (this.state.selecting && !this.state.loading){
      return(
        <EmptyState sty>
          <Select
            options = {this.state.themes ? this.state.themes.map(el => {return{label: `${el.name}`, value:`${el.id}`}}) : null}
            onChange={this.handleChange}
            value={this.state.selected}
            placeholder = "select a theme"
            />
          <Button primary onClick={this.assetUpdateRequest}>Add</Button>
        </EmptyState>
      )
    }
  }

  assetUpdateRequest = async () => {
    this.state.selected ? this.setState({loading: true}) : null
    var fetchUrl = "/api/" + this.state.selected;
    var method = "PUT";
    fetch(fetchUrl, { method: method })
    .then(response => response.json())
    .then(json => {
      if (json.status === 'success'){
        this.setState({redirect: true, selecting: false, loading: false})
      }
    })
  }
}

export default Selector;
ericdude4
Shopify Partner
7 1 3

I am dealing with the same problem. Did you ever figure it out?

0 Likes
robbwinkle
New Member
4 0 0

I ran into this same issue. Essentially when you have 2 stores open it will use the last browser tab that was fully refreshed.  Since it is a single page app there is no store checking until the app is reloaded.

One solution I'm looking at to solve this are using the JWT session token on every XHR request to the node.js server.  It looks like when you get the session token it has the correct store domain. Here is the session token documentation: https://shopify.dev/tutorials/authenticate-your-app-using-session-tokens.

Another option I was considering is making a request to the Shopify Admin GraphQL API for the shop name, but that would have the same issue of returning whichever shop is in the koa session cookie.  Possibly using the JWT server side to request the shop through GraphQL would be a simpler but less performant way of validating the JWT.

The cookie session does not work if interacting with 2 stores in a single page app. You need to deal with it in the browser or on each request to the server.

0 Likes