mirror of https://github.com/iconify/api.git
273 lines
7.0 KiB
JavaScript
273 lines
7.0 KiB
JavaScript
/**
|
|
* Main file to run in Node.js
|
|
*
|
|
* Run ssl.js instead of you want SSL support.
|
|
*/
|
|
"use strict";
|
|
|
|
/*
|
|
* Configuration
|
|
*/
|
|
// True if server should include default icons set
|
|
const serveDefaultIcons = true;
|
|
|
|
// Directories with json files for custom icon sets
|
|
// Use simple-svg-tools package to create json collections
|
|
const customIconDirectories = ['json'];
|
|
|
|
// HTTP port
|
|
// Run ssl.js for SSL support
|
|
const port = process.env.PORT || 3000;
|
|
|
|
// Cache configuration
|
|
const cache = 604800, // cache time in seconds
|
|
cacheMin = cache, // minimum cache refresh time in seconds
|
|
cachePrivate = false; // True if cache is private. Used in Cache-Control header in response
|
|
|
|
/*
|
|
* Main stuff
|
|
*/
|
|
const fs = require('fs'),
|
|
// Express stuff
|
|
express = require('express'),
|
|
app = express(),
|
|
|
|
// Debug stuff
|
|
version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version,
|
|
|
|
// Included files
|
|
Collections = require('./src/collections'),
|
|
|
|
// Query parser
|
|
parseQuery = require('./src/query');
|
|
|
|
// Region file to easy identify server in CDN
|
|
let region = '';
|
|
if (!region.length && process.env.region) {
|
|
region = process.env.region;
|
|
}
|
|
try {
|
|
region = fs.readFileSync('region.txt', 'utf8').trim();
|
|
} catch (err) {
|
|
}
|
|
if (region.length > 10 || !region.match(/^[a-z0-9_-]+$/i)) {
|
|
region = '';
|
|
}
|
|
|
|
// Reload key
|
|
let reloadKey = '';
|
|
try {
|
|
reloadKey = fs.readFileSync('.reload', 'utf8').trim();
|
|
} catch (err) {
|
|
}
|
|
if (reloadKey.length < 8 || reloadKey.length > 64) {
|
|
reloadKey = '';
|
|
}
|
|
|
|
// Icons module
|
|
const icons = serveDefaultIcons ? require('simple-svg-icons') : null;
|
|
|
|
// Collections list
|
|
let collections = null,
|
|
loading = true;
|
|
|
|
/**
|
|
* Load icons
|
|
*
|
|
* @returns {Promise}
|
|
*/
|
|
function loadIcons() {
|
|
return new Promise((fulfill, reject) => {
|
|
let t = Date.now(),
|
|
newCollections = new Collections(console.log);
|
|
|
|
console.log('Loading collections at ' + (new Date()).toString());
|
|
|
|
// Load default collections
|
|
if (icons !== null) {
|
|
Object.keys(icons.collections()).forEach(prefix => {
|
|
newCollections.addFile(icons.locate(prefix));
|
|
});
|
|
}
|
|
|
|
// Add collections from "json" directory
|
|
customIconDirectories.forEach(dir => {
|
|
newCollections.addDirectory(dir);
|
|
});
|
|
|
|
newCollections.load().then(() => {
|
|
console.log('Loaded in ' + (Date.now() - t) + 'ms');
|
|
fulfill(newCollections);
|
|
}).catch(err => {
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parse request
|
|
*
|
|
* @param {string} prefix
|
|
* @param {string} query
|
|
* @param {string} ext
|
|
* @param {object} req
|
|
* @param {object} res
|
|
*/
|
|
function parseRequest(prefix, query, ext, req, res) {
|
|
function parse() {
|
|
let result = 404,
|
|
collection = collections.find(prefix);
|
|
|
|
if (collection !== null) {
|
|
result = parseQuery(collection, query, ext, req.query);
|
|
}
|
|
|
|
if (typeof result === 'number') {
|
|
res.sendStatus(result);
|
|
return;
|
|
}
|
|
|
|
// Send cache header
|
|
if (
|
|
cache &&
|
|
(req.get('Pragma') === void 0 || req.get('Pragma').indexOf('no-cache') === -1) &&
|
|
(req.get('Cache-Control') === void 0 || req.get('Cache-Control').indexOf('no-cache') === -1)
|
|
) {
|
|
res.set('Cache-Control', (cachePrivate ? 'private' : 'public') + ', max-age=' + cache + ', min-refresh=' + cacheMin);
|
|
if (!cachePrivate) {
|
|
res.set('Pragma', 'cache');
|
|
}
|
|
}
|
|
|
|
// Check for download
|
|
if (result.filename !== void 0 && (req.query.download === '1' || req.query.download === 'true')) {
|
|
res.set('Content-Disposition', 'attachment; filename="' + result.filename + '"');
|
|
}
|
|
|
|
// Send data
|
|
res.type(result.type).send(result.body);
|
|
}
|
|
|
|
// Parse query
|
|
if (collections === null) {
|
|
// This means script is still loading
|
|
// Attempt to parse query every 100ms for up to 2 seconds
|
|
let attempts = 0,
|
|
timer = setInterval(function() {
|
|
attempts ++;
|
|
if (collections === null) {
|
|
if (attempts > 20) {
|
|
clearInterval(timer);
|
|
res.sendStatus(503);
|
|
}
|
|
} else {
|
|
clearInterval(timer);
|
|
parse();
|
|
}
|
|
}, 100);
|
|
|
|
} else {
|
|
parse();
|
|
}
|
|
}
|
|
|
|
// Load icons
|
|
loadIcons().then(newCollections => {
|
|
collections = newCollections;
|
|
loading = false;
|
|
}).catch(err => {
|
|
console.log('Fatal error loading collections:', err);
|
|
loading = false;
|
|
});
|
|
|
|
// Disable X-Powered-By header
|
|
app.disable('x-powered-by');
|
|
|
|
// CORS
|
|
app.options('/*', (req, res) => {
|
|
res.header('Access-Control-Allow-Origin', '*');
|
|
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding');
|
|
res.header('Access-Control-Max-Age', 86400);
|
|
res.send(200);
|
|
});
|
|
|
|
// GET 3 part request
|
|
app.get(/^\/([a-z0-9-]+)\/([a-z0-9-]+)\.(js|json|svg)$/, (req, res) => {
|
|
// prefix/icon.svg
|
|
// prefix/icons.json
|
|
parseRequest(req.params[0], req.params[1], req.params[2], req, res);
|
|
});
|
|
|
|
// GET 2 part JS/JSON request
|
|
app.get(/^\/([a-z0-9-]+)\.(js|json)$/, (req, res) => {
|
|
// prefix.json
|
|
parseRequest(req.params[0], 'icons', req.params[1], req, res);
|
|
});
|
|
|
|
// GET 2 part SVG request
|
|
app.get(/^\/([a-z0-9:-]+)\.svg$/, (req, res) => {
|
|
let parts = req.params[0].split(':');
|
|
|
|
if (parts.length === 2) {
|
|
// prefix:icon.svg
|
|
parseRequest(parts[0], parts[1], 'svg', req, res);
|
|
return;
|
|
}
|
|
|
|
if (parts.length === 1) {
|
|
parts = parts[0].split('-');
|
|
if (parts.length > 1) {
|
|
// prefix-icon.svg
|
|
parseRequest(parts.shift(), parts.join('-'), 'svg', req, res);
|
|
return;
|
|
}
|
|
}
|
|
|
|
res.sendStatus(404);
|
|
});
|
|
|
|
// Debug information and AWS health check
|
|
app.get('/version', (req, res) => {
|
|
let body = 'SimpleSVG CDN version ' + version + ' (Node';
|
|
if (region.length) {
|
|
body += ', ' + region;
|
|
}
|
|
body += ')';
|
|
res.send(body);
|
|
});
|
|
|
|
// Reload collections without restarting app
|
|
app.get('/reload', (req, res) => {
|
|
if (reloadKey.length && req.query && req.query.key && req.query.key === reloadKey) {
|
|
// Reload collections
|
|
process.nextTick(() => {
|
|
if (loading) {
|
|
return;
|
|
}
|
|
loading = true;
|
|
loadIcons().then(newCollections => {
|
|
collections = newCollections;
|
|
loading = false;
|
|
}).catch(err => {
|
|
console.log('Fatal error loading collections:', err);
|
|
loading = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
res.sendStatus(200);
|
|
});
|
|
|
|
// Redirect home page
|
|
app.get('/', (req, res) => {
|
|
res.redirect(301, 'https://simplesvg.com/');
|
|
});
|
|
|
|
// Create server
|
|
app.listen(port, () => {
|
|
console.log('Listening on port ' + port);
|
|
});
|
|
|
|
module.exports = app;
|