3

I have a page that uses amp-list to dynamically list products using a JSON file. I have my directory structure as follows: *root*/amp/product-name/ and then within the product-name directory I have an index.html with the AMP HTML and a products.json which contains the product data that I display on page. Everything is working as expected if I visit the page directly: https://example.com/amp/product-name/

The problem comes in when I visit from the ampproject.org CDN.

Here's the actual page, if you visit the console you can see there is a CORS error. How can I handle this? Documentation is not clear on how to handle exactly, just a very vague overview, so for someone new this doesn't make sense to me and I can't just intuit how to do it. I know I need to have a request handler of some sort, which I've partially made by copying the code from AMP's own app.js they link to in their documentation. Within the amp-list element I previously had the src pointed directly to the JSON file, but now I'm seeing that I need to point to a handler (like a JS file) and then have the handler set the request header and then output the proper JSON.

Here's what I have for the request handler:

/**
 * @param {string} url
 * @param {string} param
 * @param {*} value
 * @return {string}
 */
function addQueryParam(url, param, value) {
  const paramValue = encodeURIComponent(param) + '=' + encodeURIComponent(value);
  if (!url.includes('?')) {
    url += '?' + paramValue;
  } else {
    url += '&' + paramValue;
  }
  return url;
}

function enableCors(req, res, origin, opt_exposeHeaders) {
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Origin', 'https://www-perfectimprints-com.cdn.ampproject.org');
  res.setHeader('Access-Control-Expose-Headers', ['AMP-Access-Control-Allow-Source-Origin'].concat(opt_exposeHeaders || []).join(', '));
  if (req.query.__amp_source_origin) {
    res.setHeader('AMP-Access-Control-Allow-Source-Origin', req.query.__amp_source_origin);
  }
}

function assertCors(req, res, opt_validMethods, opt_exposeHeaders, opt_ignoreMissingSourceOrigin) {
  // Allow disable CORS check (iframe fixtures have origin 'about:srcdoc').
  if (req.query.cors == '0') {
    return;
  }
  const validMethods = opt_validMethods || ['GET', 'POST', 'OPTIONS'];
  const invalidMethod = req.method + ' method is not allowed. Use POST.';
  const invalidOrigin = 'Origin header is invalid.';
  const invalidSourceOrigin = '__amp_source_origin parameter is invalid.';
  const unauthorized = 'Unauthorized Request';
  let origin;
  if (validMethods.indexOf(req.method) == -1) {
    res.statusCode = 405;
    res.end(JSON.stringify({
      message: invalidMethod
    }));
    throw invalidMethod;
  }
  if (req.headers.origin) {
    origin = req.headers.origin;
    if (!ORIGIN_REGEX.test(req.headers.origin)) {
      res.statusCode = 500;
      res.end(JSON.stringify({
        message: invalidOrigin
      }));
      throw invalidOrigin;
    }
    if (!opt_ignoreMissingSourceOrigin && !SOURCE_ORIGIN_REGEX.test(req.query.__amp_source_origin)) {
      res.statusCode = 500;
      res.end(JSON.stringify({
        message: invalidSourceOrigin
      }));
      throw invalidSourceOrigin;
    }
  } else if (req.headers['amp-same-origin'] == 'true') {
    origin = getUrlPrefix(req);
  } else {
    res.statusCode = 401;
    res.end(JSON.stringify({
      message: unauthorized
    }));
    throw unauthorized;
  }
  enableCors(req, res, origin, opt_exposeHeaders);
}

And here's what I'm using for the HTML.

<amp-list credentials="include" width="auto" height="1500" layout="fixed-height" src="https://perfectimprints.com/amp/handle-amp-requests.js" class="m1">
      <template type="amp-mustache" id="amp-template-id">
        <div class="product" style="padding-top: 1em;">
          <a class="wrapper-link" href="{{ link }}">
            <amp-img alt="{{ title }}" width="1000" height="1000" src="{{ src }}" layout="responsive"></amp-img>
            <h3 class="product-name text-centered">
              {{title}}
            </h3>
            <h4 class="text-centered sku margin-minus-1">{{ sku }}</h4>
            <div class="text-centered get-price">
              <a class="text-centered get-price-link" href="{{ link }}">Get pricing now</a>
            </div>
          </a>
        </div>
      </template>
      <div overflow role="button" aria-label="Show more">
        Show more
       </div>
    </amp-list>

https://www-perfectimprints-com.cdn.ampproject.org/c/s/www.perfectimprints.com/amp/trick-or-treat-bags/index.html

Patrick
  • 374
  • 4
  • 21
  • The src in amp-list is only intended for the JSON source file that contains the info for your mustache tags, not JS as you currently have set. Your CORS headers need to be set in the page header information. Best bet, depending on your server setup (Apache or Nginx), is to set your CORS headers up in your htacess (Apache) or conf (Nginx) file. – Craig Scott Aug 24 '18 at 18:20
  • Okay, how would you do that? I've tried that way using .htaccess but it's not working. – Patrick Aug 24 '18 at 18:22

3 Answers3

5

This was the solution that worked for me.

Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Allow-Origin "https://example-com.cdn.ampproject.org"
Header set Access-Control-Allow-Source-Origin "https://example.com"
Header set Access-Control-Expose-Headers AMP-Access-Control-Allow-Source-Origin
Header set AMP-Access-Control-Allow-Source-Origin "https://example.com"

Thank you to @Craig Scott for the prompt to help me solve.

Woe be the poor, inexperienced soul who deals with AMP pages.

Patrick
  • 374
  • 4
  • 21
1

Presuming you're on an Apache server, you could set your CORS header in your htaccess file like this:

Header set Access-Control-Allow-Origin "https://www-perfectimprints-com.cdn.ampproject.org"
Header set Access-Control-Allow-Origin "https://www-perfectimprints-com.amp.cloudflare.com"

Then ensure your htaccess file is uploaded to the directory that contains your website HTML. That's how it's set-up on the site I manage and everything works for us.

Craig Scott
  • 892
  • 4
  • 14
  • I am on an Apache server, and I've tried this but for whatever reason it's not working. It could be due to the caching of cdn.ampproject.org, I guess. I'm at my wits end here. – Patrick Aug 24 '18 at 19:45
  • 1
    Just checked your site and it looks like a lot of your errors are cleared up? Only issue it looks to be pointing to now is a lack of SSL on your amp-list src. Like you said, the AMP link is most likely holding a cached version. Your standard link says it's AMP valid, just complaining about a lack of SSL on the amp-list src. Most reputable hosting providers offer a free SSL service nowadays through the cPanel. If yours does not though, Let's Encrypt is free: https://letsencrypt.org/. – Craig Scott Aug 25 '18 at 02:47
  • Hey Craig, thank you for checking back. I totally meant to come back and post that I had figured out the solution. I added what you had above to my .htaccess and then added the rest of the things it complained about in the JS console one by one until it stopped complaining. Thank you for your help (Again! You helped me previously). As far as the SSL, it must be because I referenced the source like `src="products.json"` instead of a more direct link like `src="https://example.com/amp/product-name/products.json"`. Thanks for the tip, I will update to the proper link on Monday. – Patrick Aug 25 '18 at 04:09
0

For people using nodejs, here's the syntax:

        res.set('AMP-Redirect-To', encodedUrl);
        res.set('Access-Control-Allow-Origin', websiteHostname);
        res.set('AMP-Access-Control-Allow-Source-Origin', websiteHostname);
        res.set('Access-Control-Expose-Headers', [
            'AMP-Access-Control-Allow-Source-Origin',
            'AMP-Redirect-To',
        ]);
        res.set('Access-Control-Allow-Credentials', 'true');

ِAnother solution is to use the amp-toolbox-cors library and set it's verifyOrigin option to false.

import ampCors from 'amp-toolbox-cors';

app.use(
    ampCors({
        verifyOrigin: false,
    }),
);

beware that all origins are allowed like that. In reality you should allow just two.

Hope it saves somebody's time in the future!

Omar Dulaimi
  • 846
  • 10
  • 30