craftcms / element-api

Create a JSON API/Feed for your elements in Craft.
MIT License
498 stars 56 forks source link

JS fetch request works from Craft frontend, but not from a different subdomain #182

Open Michael-Paragonn opened 11 months ago

Michael-Paragonn commented 11 months ago

Description

A client has an Element API endpoint set up to return quick "as you type" search results. This works beautifully from the Craft site (https://example.com).

The client also has a subdomain powered by HubSpot (https://learn.example.com), and they want the same functionality across both sites. But when I copy over the same exact JS, the returned response is supposedly a 404, even though the JSON payload is identical.

Any ideas on why it's not working from a subdomain? I do have Access-Control-Allow-Origin defined properly, and I do see the difference it makes when it's there vs not there...

element-api.php

<?php
use craft\elements\Entry;

return
[
    'endpoints' =>
    [
        'api/search' => function()
        {
            Craft::$app->getResponse()->getHeaders()->set('Access-Control-Allow-Origin', 'https://learn.example.com');
            Craft::$app->getResponse()->getHeaders()->set('Access-Control-Allow-Credentials', true);

            // settings
            $section_handle = 'about, blog, caseStudies, industries, services, work';
            $phrase = Craft::$app->request->getParam('query');

            $criteria = [
                'section' => $section_handle,
                'limit' => 5,
                'orderBy' => 'score',
                'search' => $phrase,

            ];

            return [
                'elementType' => Entry::class,
                'criteria' => $criteria,
                'paginate' => false,
                'transformer' => function(craft\elements\Entry $entry)
                {

                    return [
                        'title' => $entry->title,
                        'url' => $entry->section->handle == 'blog' ? $entry->permalink : $entry->url,
                        'section' => $entry->section->name,
                    ];
                },

            ];
        },
    ]
];

JS search functionality (on the HubSpot subdomain site):

function delay(fn, ms)
{
    let timer = 0
    return function(...args)
    {
        clearTimeout(timer)
        timer = setTimeout(fn.bind(this, ...args), ms || 0)
    }
}
let searchAutocompleteInputs = document.querySelectorAll('.jsSearchAutocomplete');
searchAutocompleteInputs.forEach(input =>
{
    let autocompleteId = input.dataset.list;
    let autocomplete = document.getElementById(autocompleteId);
    input.addEventListener('input', delay( function(e)
    {
        let keyword = e.target.value;
        if (keyword)
        {
            fetch('https://example.com/api/search' + `?query=${keyword}`, {mode: 'cors'})
            .then((response) =>
            {
                console.log({response});
                if (response.ok)
                {
                    return response.json();
                }
                throw new Error('Something went wrong with the fetch');
            })
            .then( (responseJson) =>
            {
                               // blah blah...
            })
            .catch((error) =>
            {
                console.warn(error);
            });
        }
        // Empty search field
        else
        {
            autocomplete.innerHTML = '';
        }
    }, 500));
});

"404" response from Hubspot site:

# General
- Request URL: https://example.com/api/search?query=logo
- Request Method: GET
- Status Code: 404
- Remote Address: [redacted]:443
- Referrer Policy: no-referrer-when-downgrade
# Response Headers
- Access-Control-Allow-Credentials: 1
- Access-Control-Allow-Origin: https://learn.example.com
- Cf-Cache-Status: DYNAMIC
- Cf-Ray: 7f43074238c72bca-FRA
- Content-Encoding: br
- Content-Type: application/json; charset=UTF-8
- Date: Wed, 09 Aug 2023 21:17:52 GMT
- Link: <https://example.com/api/search>; rel="canonical"
- Nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
- Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=blahblahblah"}],"group":"cf-nel","max_age":604800}
- Server: cloudflare
- Vary: Accept-Encoding
- X-Powered-By: Craft CMS
- X-Robots-Tag: none
# Request Headers
- :authority: example.com
- :method: GET
- :path: /api/search?query=logo
- :scheme: https
- Accept: */*
- Accept-Encoding: gzip, deflate, br
- Accept-Language: en-US,en;q=0.9,he;q=0.8,ar;q=0.7
- Origin: https://learn.example.com
- Referer: https://learn.example.com/?hsDebug=true
- Sec-Ch-Ua: "Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"
- Sec-Ch-Ua-Mobile: ?0
- Sec-Ch-Ua-Platform: "Windows"
- Sec-Fetch-Dest: empty
- Sec-Fetch-Mode: cors
- Sec-Fetch-Site: same-site
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36

Valid response from the Craft site:

# General
- Request URL: https://example.com/api/search?query=logo
- Request Method: GET
- Status Code: 200
- Remote Address: [redacted]:443
- Referrer Policy: strict-origin-when-cross-origin
# Response Headers
- Access-Control-Allow-Credentials: 1
- Access-Control-Allow-Origin: https://learn.example.com
- Cache-Control: no-store, no-cache, must-revalidate
- Cf-Cache-Status: DYNAMIC
- Cf-Ray: 7f4301de1ad82bc1-FRA
- Content-Encoding: br
- Content-Type: application/json; charset=UTF-8
- Date: Wed, 09 Aug 2023 21:14:11 GMT
- Expires: Thu, 19 Nov 1981 08:52:00 GMT
- Link: <https://example.com/api/search>; rel="canonical"
- Nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
- Pragma: no-cache
- Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=blahblah"}],"group":"cf-nel","max_age":604800}
- Server: cloudflare
- Set-Cookie: blahblah expires=Wed, 23-Aug-2023 21:14:11 GMT; Max-Age=1209600; path=/; secure; HttpOnly
- Vary: Accept-Encoding
- X-Debug-Duration: 211
- X-Debug-Link: https://example.com/actions/debug/default/view?tag=64d401a3b341e
- X-Debug-Tag: 64d401a3b341e
- X-Powered-By: Craft CMS
- X-Robots-Tag: none
# Request Headers
- :authority: example.com
- :method: GET
- :path: /api/search?query=logo
- :scheme: https
- Accept: */*
- Accept-Encoding: gzip, deflate, br
- Accept-Language: en-US,en;q=0.9,he;q=0.8,ar;q=0.7
- Cookie: blahblah
- Referer: https://example.com/
- Sec-Ch-Ua: "Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"
- Sec-Ch-Ua-Mobile: ?0
- Sec-Ch-Ua-Platform: "Windows"
- Sec-Fetch-Dest: empty
- Sec-Fetch-Mode: cors
- Sec-Fetch-Site: same-origin
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36

Additional info