summaryrefslogtreecommitdiff
path: root/packages/integrations/partytown/src/sirv.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/partytown/src/sirv.ts')
-rw-r--r--packages/integrations/partytown/src/sirv.ts241
1 files changed, 241 insertions, 0 deletions
diff --git a/packages/integrations/partytown/src/sirv.ts b/packages/integrations/partytown/src/sirv.ts
new file mode 100644
index 000000000..860a715bf
--- /dev/null
+++ b/packages/integrations/partytown/src/sirv.ts
@@ -0,0 +1,241 @@
+// TODO: The below has been modified from the original sirv package to support
+// the feature of mounting the served files from a certain path (in this case, `/~partytown/`)
+// It would be good to bring this into Astro for all integrations to take advantage of,
+// and potentially also to respect your config automatically for things like `base` path.
+// @ts-nocheck
+
+/**
+ * @license
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (https://lukeed.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+import * as fs from 'fs';
+import { join, normalize, resolve } from 'path';
+// import { totalist } from 'totalist/sync';
+// import { parse } from '@polka/url';
+import { lookup } from 'mrmime';
+import { URL } from 'url';
+
+const noop = () => {};
+
+function isMatch(uri, arr) {
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i].test(uri)) return true;
+ }
+}
+
+function toAssume(uri, extns) {
+ let i = 0,
+ x,
+ len = uri.length - 1;
+ if (uri.charCodeAt(len) === 47) {
+ uri = uri.substring(0, len);
+ }
+
+ let arr = [],
+ tmp = `${uri}/index`;
+ for (; i < extns.length; i++) {
+ x = extns[i] ? `.${extns[i]}` : '';
+ if (uri) arr.push(uri + x);
+ arr.push(tmp + x);
+ }
+
+ return arr;
+}
+
+function viaCache(cache, uri, extns) {
+ let i = 0,
+ data,
+ arr = toAssume(uri, extns);
+ for (; i < arr.length; i++) {
+ if ((data = cache[arr[i]])) return data;
+ }
+}
+
+function viaLocal(dir, isEtag, uri, extns) {
+ let i = 0,
+ arr = toAssume(uri, extns);
+ let abs, stats, name, headers;
+ for (; i < arr.length; i++) {
+ abs = normalize(join(dir, (name = arr[i])));
+ if (abs.startsWith(dir) && fs.existsSync(abs)) {
+ stats = fs.statSync(abs);
+ if (stats.isDirectory()) continue;
+ headers = toHeaders(name, stats, isEtag);
+ headers['Cache-Control'] = isEtag ? 'no-cache' : 'no-store';
+ return { abs, stats, headers };
+ }
+ }
+}
+
+function is404(req, res) {
+ return (res.statusCode = 404), res.end();
+}
+
+function send(req, res, file, stats, headers) {
+ let code = 200,
+ tmp,
+ opts = {};
+ headers = { ...headers };
+
+ for (let key in headers) {
+ tmp = res.getHeader(key);
+ if (tmp) headers[key] = tmp;
+ }
+
+ if ((tmp = res.getHeader('content-type'))) {
+ headers['Content-Type'] = tmp;
+ }
+
+ if (req.headers.range) {
+ code = 206;
+ let [x, y] = req.headers.range.replace('bytes=', '').split('-');
+ let end = (opts.end = parseInt(y, 10) || stats.size - 1);
+ let start = (opts.start = parseInt(x, 10) || 0);
+
+ if (start >= stats.size || end >= stats.size) {
+ res.setHeader('Content-Range', `bytes */${stats.size}`);
+ res.statusCode = 416;
+ return res.end();
+ }
+
+ headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
+ headers['Content-Length'] = end - start + 1;
+ headers['Accept-Ranges'] = 'bytes';
+ }
+
+ res.writeHead(code, headers);
+ fs.createReadStream(file, opts).pipe(res);
+}
+
+const ENCODING = {
+ '.br': 'br',
+ '.gz': 'gzip',
+};
+
+function toHeaders(name, stats, isEtag) {
+ let enc = ENCODING[name.slice(-3)];
+
+ let ctype = lookup(name.slice(0, enc && -3)) || '';
+ if (ctype === 'text/html') ctype += ';charset=utf-8';
+
+ let headers = {
+ 'Content-Length': stats.size,
+ 'Content-Type': ctype,
+ 'Last-Modified': stats.mtime.toUTCString(),
+ };
+
+ if (enc) headers['Content-Encoding'] = enc;
+ if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`;
+
+ return headers;
+}
+
+export default function (dir, opts = {}) {
+ dir = resolve(dir || '.');
+
+ let mountTo = opts.mount || '';
+ let isNotFound = opts.onNoMatch || is404;
+ let setHeaders = opts.setHeaders || noop;
+
+ let extensions = opts.extensions || ['html', 'htm'];
+ let gzips = opts.gzip && extensions.map((x) => `${x}.gz`).concat('gz');
+ let brots = opts.brotli && extensions.map((x) => `${x}.br`).concat('br');
+
+ const FILES = {};
+
+ let fallback = '/';
+ let isEtag = !!opts.etag;
+ let isSPA = !!opts.single;
+ if (typeof opts.single === 'string') {
+ let idx = opts.single.lastIndexOf('.');
+ fallback += !!~idx ? opts.single.substring(0, idx) : opts.single;
+ }
+
+ let ignores = [];
+ if (opts.ignores !== false) {
+ ignores.push(/[/]([A-Za-z\s\d~$._-]+\.\w+){1,}$/); // any extn
+ if (opts.dotfiles) ignores.push(/\/\.\w/);
+ else ignores.push(/\/\.well-known/);
+ [].concat(opts.ignores || []).forEach((x) => {
+ ignores.push(new RegExp(x, 'i'));
+ });
+ }
+
+ let cc = opts.maxAge != null && `public,max-age=${opts.maxAge}`;
+ if (cc && opts.immutable) cc += ',immutable';
+ else if (cc && opts.maxAge === 0) cc += ',must-revalidate';
+
+ if (!opts.dev) {
+ totalist(dir, (name, abs, stats) => {
+ if (/\.well-known[\\+\/]/.test(name)) {
+ } // keep
+ else if (!opts.dotfiles && /(^\.|[\\+|\/+]\.)/.test(name)) return;
+
+ let headers = toHeaders(name, stats, isEtag);
+ if (cc) headers['Cache-Control'] = cc;
+
+ FILES['/' + name.normalize().replace(/\\+/g, '/')] = { abs, stats, headers };
+ });
+ }
+
+ let lookup = opts.dev ? viaLocal.bind(0, dir, isEtag) : viaCache.bind(0, FILES);
+
+ return function (req, res, next) {
+ let extns = [''];
+ let pathname = new URL(req.url, 'https://example.dev').pathname;
+ // NEW
+ if (mountTo && pathname.startsWith(mountTo)) {
+ pathname = pathname.substring(mountTo.length);
+ }
+ // NEW END
+ let val = req.headers['accept-encoding'] || '';
+ if (gzips && val.includes('gzip')) extns.unshift(...gzips);
+ if (brots && /(br|brotli)/i.test(val)) extns.unshift(...brots);
+ extns.push(...extensions); // [...br, ...gz, orig, ...exts]
+
+ if (pathname.indexOf('%') !== -1) {
+ try {
+ pathname = decodeURIComponent(pathname);
+ } catch (err) {
+ /* malform uri */
+ }
+ }
+
+ let data = lookup(pathname, extns) || (isSPA && !isMatch(pathname, ignores) && lookup(fallback, extns));
+ if (!data) return next ? next() : isNotFound(req, res);
+
+ if (isEtag && req.headers['if-none-match'] === data.headers['ETag']) {
+ res.writeHead(304);
+ return res.end();
+ }
+
+ if (gzips || brots) {
+ res.setHeader('Vary', 'Accept-Encoding');
+ }
+
+ setHeaders(res, pathname, data.stats);
+ send(req, res, data.abs, data.stats, data.headers);
+ };
+}