Skip to content

Instantly share code, notes, and snippets.

@sjelfull
Created April 26, 2022 07:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjelfull/97bb685e9c6854509e8c56eddd17e36f to your computer and use it in GitHub Desktop.
Save sjelfull/97bb685e9c6854509e8c56eddd17e36f to your computer and use it in GitHub Desktop.
Netlify Edge Functions: Geolocation example
import type { Context } from "netlify:edge";
import {
Cookie,
setCookie,
} from "https://deno.land/std@0.78.0/http/cookie.ts";
import { CookieJar } from "https://deno.land/x/cookies/mod.ts";
let bots = [
// generic
'bot', // googlebot, bingbot, telegrambot, twitterbot, yandexbot, etc.
'check',
'cloud', // cloudflare, cloudinary, etc.
'crawler',
'download',
'monitor', // monitor & monitoring
'preview', // skypeuripreview, bingpreview, yahoo link preview, etc.
'scan',
'spider', // baiduspider, 360spider, screaming frog seo spider, etc.
// search engines
'google',
'qwantify',
'yahoo',
// aggregators, messengers and social networks
'facebookexternalhit',
'flipboard',
'tumblr',
'vkshare',
'whatsapp',
// downloaders
'curl',
'perl',
'python',
'wget',
// high activity scanners
'heritrix',
'ia_archiver',
];
const createRegex = () => new RegExp(`(${bots.join('|')})`, 'i');
let isBotRegex = createRegex();
const isBot = (userAgent: string) => isBotRegex.test(userAgent);
const ERROR_COUNTRY = 'error'
const COUNTRY_COOKIE_NAME = '_geoCountry'
const COUNTRY_OVERRIDE_PARAM = '_country'
const URLS = {
canada: 'https://canada.com',
default: 'https://us.com',
}
function getUrlToRedirectTo(countryCode: string, currentUrl?: URL) {
const region = Deno.env.get('REGION') || 'default'
if (countryCode.toUpperCase() === 'CA' && region !== 'canada') {
return appendPathToAnotherUrl(URLS['canada'], currentUrl)
}
if (countryCode.toUpperCase() !== 'CA' && region === 'canada') {
return appendPathToAnotherUrl(URLS['default'], currentUrl)
}
return null
}
function appendPathToAnotherUrl(url: string, sourceUrl?: URL) {
if (!sourceUrl) {
return url
}
const target = new URL(url)
target.pathname = sourceUrl.pathname
target.search = sourceUrl.search
target.searchParams.delete(COUNTRY_OVERRIDE_PARAM)
return target.toString()
}
export default async (request: Request, context: Context): Promise<Response> => {
const region = Deno.env.get('REGION') || 'default'
const url = new URL(request.url);
const overrideCountry = url.searchParams.get(COUNTRY_OVERRIDE_PARAM)
let countryCode = overrideCountry || context.geo?.country?.code || ERROR_COUNTRY;
const countryName = context.geo?.country?.name || "somewhere in the world";
const userAgent = request.headers.get('user-agent') || ''
const isABot = isBot(userAgent)
const countryCodeFromCookie = await context.cookies.get(COUNTRY_COOKIE_NAME);
context.log({countryCode, overrideCountry, region, isABot })
// Skip API requests?
if (url.pathname.includes('/api/')) {
return context.next()
}
if (countryCode === ERROR_COUNTRY) {
context.log('Failed to resolve country')
return context.next()
}
// Return early for bots
if (isABot) {
return context.next()
}
// Set country from cookie if we are not overriding
if (countryCodeFromCookie && !overrideCountry) {
countryCode = countryCodeFromCookie
}
// @todo This causes Type error: Headers are immutable
// if (countryCodeFromCookie !== countryCode) {
// context.log('Setting cookie to: ' + countryCode)
// const response = await context.next();
// context.cookies.set({
// name: COUNTRY_COOKIE_NAME,
// value: countryCode,
// });
// return response;
// }
const result = {
overrideCountry,
country: countryCode,
}
context.log(isABot, region, result)
const redirectTo = getUrlToRedirectTo(countryCode, url)
if (!redirectTo) {
return context.next();
}
return Response.redirect(redirectTo, 301)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment