-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
amp-geo: Fall back to API for country #26407
Changes from 4 commits
f34e54d
7ebb3bf
65c41c4
7c2f2c1
b574612
c458b06
752f769
a9bf5e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,12 +48,14 @@ import {Services} from '../../../src/services'; | |
import {ampGeoPresets} from './amp-geo-presets'; | ||
|
||
import {GEO_IN_GROUP} from './amp-geo-in-group'; | ||
import {dev, userAssert} from '../../../src/log'; | ||
import {dev, user, userAssert} from '../../../src/log'; | ||
import {getMode} from '../../../src/mode'; | ||
import {isArray, isObject} from '../../../src/types'; | ||
import {isCanary} from '../../../src/experiments'; | ||
import {isJsonScriptTag} from '../../../src/dom'; | ||
import {isProxyOrigin, isSecureUrlDeprecated} from '../../../src/url'; | ||
import {tryParseJson} from '../../../src/json'; | ||
import {urls} from '../../../src/config'; | ||
|
||
/** @const */ | ||
const TAG = 'amp-geo'; | ||
|
@@ -71,6 +73,7 @@ const GROUP_PREFIX = 'amp-geo-group-'; | |
const PRE_RENDER_REGEX = new RegExp(`${COUNTRY_PREFIX}(\\w+)`); | ||
const GEO_ID = 'ampGeo'; | ||
const SERVICE_TAG = 'geo'; | ||
const API_TIMEOUT = 60; // Seconds | ||
|
||
/** | ||
* Operating Mode | ||
|
@@ -80,6 +83,7 @@ const mode = { | |
GEO_HOT_PATCH: 0, // Default mode, geo is patched by GFE when js is served | ||
GEO_PRERENDER: 1, // We've been prerendered by an AMP Cache or publisher CMS | ||
GEO_OVERRIDE: 2, // We've been overriden in test by #amp-geo=xx | ||
GEO_API: 3, // Query API when cache patching unavailable | ||
}; | ||
|
||
/** | ||
|
@@ -161,6 +165,7 @@ export class AmpGeo extends AMP.BaseElement { | |
/** | ||
* findCountry_, sets this.country_ and this.mode_ | ||
* @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc | ||
* @return {Promise} | ||
*/ | ||
findCountry_(ampdoc) { | ||
// Flag to see if we've been pre-rendered with a country | ||
|
@@ -198,6 +203,9 @@ export class AmpGeo extends AMP.BaseElement { | |
// We have a valid 2 letter ISO country | ||
this.mode_ = mode.GEO_HOT_PATCH; | ||
this.country_ = trimmedCountryMatch[1]; | ||
} else if (trimmedCountryMatch[0] === '' && urls.geoApi) { | ||
// We were not patched, but an API is available | ||
this.mode_ = mode.GEO_API; | ||
} else if (trimmedCountryMatch[0] === '' && !getMode(this.win).localDev) { | ||
// We were not patched, if we're not in dev this is an error | ||
// and we leave the country at the default 'unknown' | ||
|
@@ -207,6 +215,115 @@ export class AmpGeo extends AMP.BaseElement { | |
'GEONOTPATCHED: amp-geo served unpatched, ISO country not set' | ||
); | ||
} | ||
|
||
return this.mode_ !== mode.GEO_API | ||
? Promise.resolve() | ||
: this.fetchCountry_().then(country => { | ||
if (country) { | ||
this.country_ = country; | ||
} else { | ||
// if API request fails, leave the country at the default 'unknown' | ||
this.error_ = true; | ||
dev().error( | ||
TAG, | ||
'GEONOTPATCHED: amp-geo served unpatched and API response not valid, ISO country not set' | ||
); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Ensure API URL definition is usable and cast its type | ||
* @param {*} url | ||
* @return {?string} | ||
*/ | ||
validateApiUrl(url) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: let's make it a private method please. Thanks |
||
if (typeof url !== 'string') { | ||
user().error(TAG, 'geoApiUrl must be a string URL'); | ||
return null; | ||
} | ||
|
||
if (!isSecureUrlDeprecated(url)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm the function name tells me that it's deprecated : )
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason it's deprecated is because it doesn't work properly in the different ampdoc modes. Use |
||
user().error(TAG, 'geoApiUrl must be secure (https)'); | ||
return null; | ||
} | ||
|
||
if (isProxyOrigin(url)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ProxyOrigin doesn't work the same reason any random url doesn't work. I don't think we need a special check for this. Let me know if you think differently. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Admittedly, there's not much reason for this check. It can be removed. I will wait for a final decision on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's needed. |
||
user().error(TAG, 'geoApiUrl cannot point to the AMP project CDN'); | ||
return null; | ||
} | ||
|
||
return url; | ||
} | ||
|
||
/** | ||
* Fetch country from API defined in config.urls | ||
* | ||
* JSON schema of Geo API response - version 0.1: | ||
* { | ||
* "$schema": "http://json-schema.org/draft-07/schema#", | ||
* "type": "object", | ||
* "properties": { | ||
* "country": { | ||
* "type": "string", | ||
* "title": "ISO 3166-1 alpha-2 (case insensitive) country code of client request", | ||
* "default": "", | ||
* "pattern": "^[a-zA-Z]{2}$" | ||
* } | ||
* }, | ||
* "required": [ | ||
* "country" | ||
* ] | ||
* } | ||
* | ||
* Sample response: | ||
* { | ||
* "country": "de" | ||
* } | ||
* | ||
* @return {Promise<?string>} | ||
*/ | ||
fetchCountry_() { | ||
const url = this.validateApiUrl(urls.geoApi); | ||
if (!url) { | ||
return Promise.resolve(null); | ||
} | ||
|
||
user().info( | ||
TAG, | ||
'API request is being used for country, this may result in FOUC' | ||
); | ||
|
||
return Services.timerFor(this.win) | ||
.timeoutPromise( | ||
API_TIMEOUT * 1000, | ||
Services.xhrFor(this.win) | ||
.fetchJson(url, { | ||
mode: 'cors', | ||
method: 'GET', | ||
credentials: 'omit', | ||
}) | ||
.then(res => res.json()) | ||
.then(json => { | ||
if (!/^[a-z]{2}$/i.test(json['country'])) { | ||
user().error( | ||
TAG, | ||
'Invalid API response, expected schema not matched for property "country"' | ||
); | ||
return null; | ||
} | ||
return json['country'].toLowerCase(); | ||
}) | ||
.catch(reason => { | ||
user().error(TAG, 'XHR country request failed', reason); | ||
return null; | ||
}), | ||
`Timeout (${API_TIMEOUT} sec) reached waiting for API response` | ||
) | ||
.catch(error => { | ||
user().error(TAG, error); | ||
return null; | ||
}); | ||
} | ||
|
||
/** | ||
|
@@ -306,7 +423,9 @@ export class AmpGeo extends AMP.BaseElement { | |
.whenReady() | ||
.then(() => ampdoc.waitForBodyOpen()) | ||
.then(body => { | ||
self.findCountry_(ampdoc); | ||
return self.findCountry_(ampdoc).then(() => body); | ||
}) | ||
.then(body => { | ||
self.matchCountryGroups_(config); | ||
|
||
let classesToRemove = []; | ||
|
@@ -316,6 +435,7 @@ export class AmpGeo extends AMP.BaseElement { | |
classesToRemove = self.clearPreRender_(body); | ||
// Intentionally fall through. | ||
case mode.GEO_HOT_PATCH: | ||
case mode.GEO_API: | ||
// Build the AMP State, add classes | ||
states.ISOCountry = self.country_; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd prefer to handle one mode in one if statement block. Can we please change the logic to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!