mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 17:45:52 -05:00
291 lines
12 KiB
JavaScript
291 lines
12 KiB
JavaScript
const async = require('async');
|
|
const defaults = require('lodash.defaults');
|
|
const fastlyConfig = require('./lib/fastly-config-methods');
|
|
const languages = require('scratch-l10n').default;
|
|
|
|
const routeJson = require('../src/routes.json');
|
|
|
|
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
|
|
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
|
|
const RADISH_URL = process.env.RADISH_URL || '';
|
|
|
|
const fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY_SERVICE_ID);
|
|
|
|
const extraAppRoutes = [
|
|
// Homepage with querystring.
|
|
// TODO: Should this be added for every route?
|
|
'/\\?',
|
|
// View html
|
|
'/[^/]*.html$'
|
|
];
|
|
|
|
const routeJsonPreProcessed = routeJson.map(
|
|
route => {
|
|
if (route.redirect) {
|
|
process.stdout.write(`Updating: ${route.redirect} to `);
|
|
route.redirect = route.redirect.replace('RADISH_URL', RADISH_URL);
|
|
process.stdout.write(`${route.redirect}\n`);
|
|
}
|
|
return route;
|
|
}
|
|
);
|
|
const routes = routeJsonPreProcessed.map(
|
|
route => defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route)
|
|
);
|
|
|
|
async.auto({
|
|
version: function (cb) {
|
|
fastly.getLatestActiveVersion((err, response) => {
|
|
if (err) return cb(err);
|
|
// Validate latest version before continuing
|
|
if (response.active || response.locked) {
|
|
fastly.cloneVersion(response.number, (e, resp) => {
|
|
if (e) return cb(`Failed to clone latest version: ${e}`);
|
|
cb(null, resp.number);
|
|
});
|
|
} else {
|
|
cb(null, response.number);
|
|
}
|
|
});
|
|
},
|
|
recvCustomVCL: ['version', function (results, cb) {
|
|
// For all the routes in routes.json, construct a varnish-style regex that matches
|
|
// on any of those route conditions.
|
|
const notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
|
|
|
|
// For a non-pass condition, point backend at s3
|
|
const recvCondition = `${'' +
|
|
'if ('}${notPassStatement}) {\n` +
|
|
` set req.backend = F_s3;\n` +
|
|
` set req.http.host = "${S3_BUCKET_NAME}";\n` +
|
|
`} else {\n` +
|
|
` if (!req.http.Fastly-FF) {\n` +
|
|
` if (req.http.X-Forwarded-For) {\n` +
|
|
` set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n` +
|
|
` } else {\n` +
|
|
` set req.http.Fastly-Temp-XFF = client.ip;\n` +
|
|
` }\n` +
|
|
` } else {\n` +
|
|
` set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n` +
|
|
` }\n` +
|
|
` set req.grace = 60s;\n` +
|
|
` if (req.http.Cookie:scratchlanguage) {\n` +
|
|
` set req.http.Accept-Language = req.http.Cookie:scratchlanguage;\n` +
|
|
` } else {\n` +
|
|
` set req.http.Accept-Language = accept.language_lookup("${
|
|
Object.keys(languages).join(':')}", ` +
|
|
`"en", ` +
|
|
`std.tolower(req.http.Accept-Language)` +
|
|
`);\n` +
|
|
` }\n` +
|
|
` if (req.url ~ "^(/projects/|/fragment/account-nav.json|/session/)" && ` +
|
|
`!req.http.Cookie:scratchsessionsid) {\n` +
|
|
` set req.http.Cookie = "scratchlanguage=" req.http.Cookie:scratchlanguage;\n` +
|
|
` } else {\n` +
|
|
` return(pass);\n` +
|
|
` }\n` +
|
|
`}\n`;
|
|
|
|
|
|
fastly.setCustomVCL(
|
|
results.version,
|
|
'recv-condition',
|
|
recvCondition,
|
|
cb
|
|
);
|
|
}],
|
|
fetchCustomVCL: ['version', function (results, cb) {
|
|
const passStatement = fastlyConfig.negateConditionStatement(
|
|
fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname)
|
|
);
|
|
const ttlCondition = fastlyConfig.setResponseTTL(passStatement);
|
|
fastly.setCustomVCL(results.version, 'fetch-condition', ttlCondition, cb);
|
|
}],
|
|
appRouteRequestConditions: ['version', function (results, cb) {
|
|
const conditions = {};
|
|
async.forEachOf(routes, (route, id, cb2) => {
|
|
const condition = {
|
|
name: fastlyConfig.getConditionNameForRoute(route, 'request'),
|
|
statement: `req.url.path ~ "${route.pattern}"`,
|
|
type: 'REQUEST',
|
|
// Priority needs to be > 1 to not interact with http->https redirect
|
|
priority: 10 + id
|
|
};
|
|
fastly.setCondition(results.version, condition, (err, response) => {
|
|
if (err) return cb2(err);
|
|
conditions[id] = response;
|
|
cb2(null, response);
|
|
});
|
|
}, err => {
|
|
if (err) return cb(err);
|
|
cb(null, conditions);
|
|
});
|
|
}],
|
|
appRouteHeaders: ['version', 'appRouteRequestConditions', function (results, cb) {
|
|
const headers = {};
|
|
async.forEachOf(routes, (route, id, cb2) => {
|
|
if (route.redirect) {
|
|
async.auto({
|
|
responseCondition: function (cb3) {
|
|
const condition = {
|
|
name: fastlyConfig.getConditionNameForRoute(route, 'response'),
|
|
statement: `req.url.path ~ "${route.pattern}"`,
|
|
type: 'RESPONSE',
|
|
priority: id
|
|
};
|
|
fastly.setCondition(results.version, condition, cb3);
|
|
},
|
|
responseObject: function (cb3) {
|
|
const responseObject = {
|
|
name: fastlyConfig.getResponseNameForRoute(route),
|
|
status: 301,
|
|
response: 'Moved Permanently',
|
|
request_condition: fastlyConfig.getConditionNameForRoute(route, 'request')
|
|
};
|
|
fastly.setResponseObject(results.version, responseObject, cb3);
|
|
},
|
|
redirectHeader: ['responseCondition', function (redirectResults, cb3) {
|
|
const header = {
|
|
name: fastlyConfig.getHeaderNameForRoute(route),
|
|
action: 'set',
|
|
ignore_if_set: 0,
|
|
type: 'RESPONSE',
|
|
dst: 'http.Location',
|
|
src: `"${route.redirect}"`,
|
|
response_condition: redirectResults.responseCondition.name
|
|
};
|
|
fastly.setFastlyHeader(results.version, header, cb3);
|
|
}]
|
|
}, (err, redirectResults) => {
|
|
if (err) return cb2(err);
|
|
headers[id] = redirectResults.redirectHeader;
|
|
cb2(null, redirectResults);
|
|
});
|
|
} else {
|
|
const header = {
|
|
name: fastlyConfig.getHeaderNameForRoute(route, 'request'),
|
|
action: 'set',
|
|
ignore_if_set: 0,
|
|
type: 'REQUEST',
|
|
dst: 'url',
|
|
src: `"/${route.name}.html"`,
|
|
request_condition: results.appRouteRequestConditions[id].name,
|
|
priority: 10
|
|
};
|
|
fastly.setFastlyHeader(results.version, header, (err, response) => {
|
|
if (err) return cb2(err);
|
|
headers[id] = response;
|
|
cb2(null, response);
|
|
});
|
|
}
|
|
}, err => {
|
|
if (err) return cb(err);
|
|
cb(null, headers);
|
|
});
|
|
}],
|
|
tipbarRedirectHeaders: ['version', function (results, cb) {
|
|
async.auto({
|
|
requestCondition: function (cb2) {
|
|
const condition = {
|
|
name: 'routes/?tip_bar= (request)',
|
|
statement: 'req.url ~ "\\?tip_bar="',
|
|
type: 'REQUEST',
|
|
priority: 10
|
|
};
|
|
fastly.setCondition(results.version, condition, cb2);
|
|
},
|
|
responseCondition: function (cb2) {
|
|
const condition = {
|
|
name: 'routes/?tip_bar= (response)',
|
|
statement: 'req.url ~ "\\?tip_bar="',
|
|
type: 'RESPONSE',
|
|
priority: 10
|
|
};
|
|
fastly.setCondition(results.version, condition, cb2);
|
|
},
|
|
responseObject: ['requestCondition', function (redirectResults, cb2) {
|
|
const responseObject = {
|
|
name: 'redirects/?tip_bar=',
|
|
status: 301,
|
|
response: 'Moved Permanently',
|
|
request_condition: redirectResults.requestCondition.name
|
|
};
|
|
fastly.setResponseObject(results.version, responseObject, cb2);
|
|
}],
|
|
redirectHeader: ['responseCondition', function (redirectResults, cb2) {
|
|
const header = {
|
|
name: 'redirects/?tip_bar=',
|
|
action: 'set',
|
|
ignore_if_set: 0,
|
|
type: 'RESPONSE',
|
|
dst: 'http.Location',
|
|
src: 'regsub(req.url, "tip_bar=", "tutorial=")',
|
|
response_condition: redirectResults.responseCondition.name
|
|
};
|
|
fastly.setFastlyHeader(results.version, header, cb2);
|
|
}]
|
|
}, (err, redirectResults) => {
|
|
if (err) return cb(err);
|
|
cb(null, redirectResults);
|
|
});
|
|
}],
|
|
embedRedirectHeaders: ['version', function (results, cb) {
|
|
async.auto({
|
|
requestCondition: function (cb2) {
|
|
const condition = {
|
|
name: 'routes/projects/embed (request)',
|
|
statement: 'req.url.path ~ "^/projects/embed/(\\d+)"',
|
|
type: 'REQUEST',
|
|
priority: 10
|
|
};
|
|
fastly.setCondition(results.version, condition, cb2);
|
|
},
|
|
responseCondition: function (cb2) {
|
|
const condition = {
|
|
name: 'routes/projects/embed (response)',
|
|
statement: 'req.url.path ~ "^/projects/embed/(\\d+)"',
|
|
type: 'RESPONSE',
|
|
priority: 10
|
|
};
|
|
fastly.setCondition(results.version, condition, cb2);
|
|
},
|
|
responseObject: ['requestCondition', function (redirectResults, cb2) {
|
|
const responseObject = {
|
|
name: 'redirects/projects/embed',
|
|
status: 301,
|
|
response: 'Moved Permanently',
|
|
request_condition: redirectResults.requestCondition.name
|
|
};
|
|
fastly.setResponseObject(results.version, responseObject, cb2);
|
|
}],
|
|
redirectHeader: ['responseCondition', function (redirectResults, cb2) {
|
|
const header = {
|
|
name: 'redirects/projects/embed',
|
|
action: 'set',
|
|
ignore_if_set: 0,
|
|
type: 'RESPONSE',
|
|
dst: 'http.Location',
|
|
src: '"/projects/" + re.group.1 + "/embed"',
|
|
response_condition: redirectResults.responseCondition.name
|
|
};
|
|
fastly.setFastlyHeader(results.version, header, cb2);
|
|
}]
|
|
}, (err, redirectResults) => {
|
|
if (err) return cb(err);
|
|
cb(null, redirectResults);
|
|
});
|
|
}]
|
|
}, (err, results) => {
|
|
if (err) throw new Error(err);
|
|
if (process.env.FASTLY_ACTIVATE_CHANGES) {
|
|
fastly.activateVersion(results.version, (e, resp) => {
|
|
if (e) throw new Error(e);
|
|
process.stdout.write(`Successfully configured and activated version ${resp.number}\n`);
|
|
// purge static-assets using surrogate key
|
|
fastly.purgeKey(FASTLY_SERVICE_ID, 'static-assets', error => {
|
|
if (error) throw new Error(error);
|
|
process.stdout.write('Purged static assets.\n');
|
|
});
|
|
});
|
|
}
|
|
});
|