@africanpilot/next-snapshot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,195 @@
1
+ // The bundle's transformations, as pure functions of their input so each can be
2
+ // tested without a capture on disk.
3
+ //
4
+ // createRewriter HTML and CSS: asset URLs become __NOA<n>__ tokens the
5
+ // runtime swaps for blob: URLs; <base> + shim go first.
6
+ // virtualiseLocation JS: every global `location` becomes `__NOloc`.
7
+ // serialisePost offline.post handlers, as source text for the file.
8
+
9
+ import { transform } from "esbuild";
10
+
11
+ import { urlKey } from "./key.js";
12
+
13
+ const TAG = String.raw`<[a-zA-Z][^\s/>]*(?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>\x60]+))?)*\s*/?>`;
14
+ const HTML_RE = new RegExp(
15
+ String.raw`(<script\b(?:[^>"']|"[^"]*"|'[^']*')*>)([\s\S]*?)(</script\s*>)|(<style\b[^>]*>)([\s\S]*?)(</style\s*>)|<!--[\s\S]*?-->|${TAG}`,
16
+ "gi",
17
+ );
18
+ const ATTR_RE = /(\s)(src|href|srcset|imagesrcset|poster|xlink:href|style)(\s*=\s*)("([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi;
19
+
20
+ const LOCATION_DEFINES = {
21
+ location: "__NOloc",
22
+ "window.location": "__NOloc",
23
+ "self.location": "__NOloc",
24
+ "globalThis.location": "__NOloc",
25
+ "document.location": "__NOloc",
26
+ };
27
+
28
+ /** Maps a URL on any alias of the app's origin onto the origin itself. */
29
+ export function canonicalizer(origin, aliases = []) {
30
+ return (u) => {
31
+ for (const a of aliases) if (u === a || u.startsWith(a + "/") || u.startsWith(a + "?")) return origin + u.slice(a.length);
32
+ return u;
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @param {object} o
38
+ * @param {string} o.origin the captured app's origin
39
+ * @param {string[]} [o.aliases] other spellings of that origin
40
+ * @param {(key: string, variant: string|null) => number} o.lookup asset index, or -1
41
+ * @param {string} [o.pageCSS] markup appended after the shim in every page
42
+ * @param {(key: string) => void} [o.onMiss] told of each reference to nothing captured
43
+ */
44
+ export function createRewriter({ origin, aliases = [], lookup, pageCSS = "", onMiss = () => {} }) {
45
+ const canon = canonicalizer(origin, aliases);
46
+
47
+ function tokenFor(raw, base, v) {
48
+ const clean = decodeEntities(raw.trim());
49
+ if (!clean || /^(data:|blob:|javascript:|about:|mailto:|tel:|#)/i.test(clean)) return null;
50
+ let abs;
51
+ try {
52
+ abs = new URL(clean, base).href;
53
+ } catch {
54
+ return null;
55
+ }
56
+ const key = urlKey(canon(abs), origin, origin);
57
+ if (!key) return null;
58
+ const i = lookup(key, v);
59
+ if (i < 0) {
60
+ onMiss(key);
61
+ return null;
62
+ }
63
+ return `__NOA${i}__`;
64
+ }
65
+
66
+ function rewriteCSS(css, base, v) {
67
+ return css
68
+ .replace(/url\(\s*(?:(["'])(.*?)\1|([^)'"\s]+))\s*\)/g, (m, _q, quoted, bare) => {
69
+ const t = tokenFor(quoted ?? bare, base, v);
70
+ return t ? `url(${t})` : m;
71
+ })
72
+ .replace(/@import\s+(["'])(.*?)\1/g, (m, _q, u) => {
73
+ const t = tokenFor(u, base, v);
74
+ return t ? `@import url(${t})` : m;
75
+ });
76
+ }
77
+
78
+ // Candidates that were never captured are dropped rather than left to fail;
79
+ // the element falls back to its `src`.
80
+ function rewriteSrcset(value, base, v) {
81
+ return value
82
+ .split(/\s*,\s+/)
83
+ .map((part) => {
84
+ const bits = part.trim().split(/\s+/);
85
+ const t = bits[0] && tokenFor(bits[0], base, v);
86
+ return t ? [t, ...bits.slice(1)].join(" ") : null;
87
+ })
88
+ .filter(Boolean)
89
+ .join(", ");
90
+ }
91
+
92
+ function rewriteTag(tag, base, v) {
93
+ const name = /^<([a-zA-Z][^\s/>]*)/.exec(tag)[1].toLowerCase();
94
+ // Navigation targets are not assets: the runtime intercepts them instead.
95
+ if (name === "a" || name === "area" || name === "form" || name === "iframe" || name === "base") return tag;
96
+ if (name === "meta") {
97
+ return /http-equiv\s*=\s*["']?refresh/i.test(tag) ? tag.replace(/http-equiv\s*=\s*(["']?)refresh\1/i, "data-no-refresh") : tag;
98
+ }
99
+ if (name === "link") {
100
+ const rel = (attr(tag, "rel") ?? "").toLowerCase();
101
+ // Hints at the network have nothing to hint at offline.
102
+ if (/\b(dns-prefetch|preconnect|manifest)\b/.test(rel)) return "";
103
+ const href = attr(tag, "href");
104
+ if (href != null && !tokenFor(href, base, v) && /\b(preload|prefetch|modulepreload|prerender)\b/.test(rel)) return "";
105
+ }
106
+ return tag.replace(ATTR_RE, (m, sp, an, eq, _all, dq, sq, bare) => {
107
+ const val = dq ?? sq ?? bare ?? "";
108
+ const lower = an.toLowerCase();
109
+ if (lower === "style") return `${sp}${an}="${escAttr(rewriteCSS(decodeEntities(val), base, v))}"`;
110
+ if (lower === "srcset" || lower === "imagesrcset") return `${sp}${an}="${escAttr(rewriteSrcset(decodeEntities(val), base, v))}"`;
111
+ const t = tokenFor(val, base, v);
112
+ return t ? `${sp}${an}${eq}"${t}"` : m;
113
+ });
114
+ }
115
+
116
+ // Inline <script> bodies are never touched: they are code (often a JSON
117
+ // payload), and a URL inside a string there is data, not a reference.
118
+ function rewriteHTML(html, key, v) {
119
+ const base = origin + key;
120
+ let body = html.replace(HTML_RE, (m, so, sb, sc, sto, stb, stc) => {
121
+ if (so) return rewriteTag(so, base, v) + sb + sc;
122
+ if (sto) return sto + rewriteCSS(stb, base, v) + stc;
123
+ if (m.startsWith("<!--")) return m;
124
+ return rewriteTag(m, base, v);
125
+ });
126
+ const inject = `<base href="${escAttr(base)}"><script data-no-shim></script>${pageCSS}`;
127
+ if (/<head\b[^>]*>/i.test(body)) body = body.replace(/<head\b[^>]*>/i, (h) => h + inject);
128
+ else if (/<html\b[^>]*>/i.test(body)) body = body.replace(/<html\b[^>]*>/i, (h) => h + "<head>" + inject + "</head>");
129
+ else body = inject + body;
130
+ return body;
131
+ }
132
+
133
+ return { tokenFor, rewriteCSS, rewriteSrcset, rewriteTag, rewriteHTML };
134
+ }
135
+
136
+ /**
137
+ * Rewrite every reference to the global `location` to `__NOloc`. esbuild's
138
+ * `define` only replaces unbound identifiers, so strings and a local variable
139
+ * that happens to be called `location` are left alone. Throws if the code does
140
+ * not parse.
141
+ */
142
+ export async function virtualiseLocation(code) {
143
+ const r = await transform(code, {
144
+ loader: "js",
145
+ define: LOCATION_DEFINES,
146
+ minifyWhitespace: true,
147
+ legalComments: "none",
148
+ charset: "utf8",
149
+ target: "esnext",
150
+ logLevel: "silent",
151
+ });
152
+ return r.code;
153
+ }
154
+
155
+ /**
156
+ * offline.post handlers as the source of an object literal. They run in the
157
+ * browser, so they are serialised with Function#toString — which is why method
158
+ * shorthand (`signin(form) {}`) is refused: its source is not an expression.
159
+ */
160
+ export function serialisePost(post = {}) {
161
+ const entries = Object.entries(post).map(([p, fn]) => {
162
+ if (typeof fn !== "function") throw new Error(`offline.post["${p}"] must be a function`);
163
+ const src = fn.toString();
164
+ if (!/^(async\s*)?(\(|function\b|[A-Za-z_$][\w$]*\s*=>)/.test(src)) {
165
+ throw new Error(`offline.post["${p}"] must be an arrow function or function expression (method shorthand cannot be serialised)`);
166
+ }
167
+ return `${JSON.stringify(p)}: (${src})`;
168
+ });
169
+ return `{${entries.join(",\n")}}`;
170
+ }
171
+
172
+ export function attr(tag, name) {
173
+ const m = new RegExp(`\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>\`]+))`, "i").exec(tag);
174
+ return m ? decodeEntities(m[1] ?? m[2] ?? m[3] ?? "") : null;
175
+ }
176
+
177
+ export function decodeEntities(s) {
178
+ return s
179
+ .replace(/&amp;/g, "&")
180
+ .replace(/&quot;/g, '"')
181
+ .replace(/&#x27;|&#39;|&apos;/g, "'")
182
+ .replace(/&lt;/g, "<")
183
+ .replace(/&gt;/g, ">")
184
+ .replace(/&#x2F;|&#47;/gi, "/")
185
+ .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(+n))
186
+ .replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)));
187
+ }
188
+
189
+ export function escAttr(s) {
190
+ return String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
191
+ }
192
+
193
+ export function escHTML(s) {
194
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
195
+ }
@@ -0,0 +1,404 @@
1
+ // Runs first in every page frame, before any of the app's own scripts.
2
+ //
3
+ // The page believes it is at its real URL (http://host/path?query). It is
4
+ // actually in an about:srcdoc frame inside a file:// document, with no server.
5
+ // This shim closes that gap:
6
+ //
7
+ // location the bundler rewrote every `location` reference in the app's
8
+ // scripts to `__NOloc`; this defines it, reporting the virtual URL
9
+ // and turning assignments into snapshot navigations.
10
+ // history pushState/replaceState record the virtual URL and mirror it into
11
+ // the outer page's hash, instead of throwing on a path change.
12
+ // fetch/XHR served from the snapshot. Next's RSC requests get a non-RSC
13
+ // answer on purpose: Next then falls back to a full navigation,
14
+ // which the shell turns into a fresh frame of the captured HTML.
15
+ // URLs any script/link/img src the app sets at runtime is mapped to the
16
+ // asset's blob: URL; reads give the original back, so a chunk
17
+ // loader that looks itself up by src still finds itself.
18
+ // clicks plain same-origin links and GET forms navigate in the snapshot;
19
+ // POST forms go to the configured handlers or are refused.
20
+ //
21
+ // Must not contain the character sequence that closes a script element.
22
+ (function () {
23
+ "use strict";
24
+ var W = window, P = W.parent, NO = P && P.__NO;
25
+ if (!NO || !NO.frameState) return;
26
+ var SEQ = NO.frameState.seq;
27
+ var cur = new URL(NO.frameState.url);
28
+ var D = W.document;
29
+ var EP = W.Element.prototype, ga = EP.getAttribute, sa = EP.setAttribute, san = EP.setAttributeNS;
30
+
31
+ function abs(u) { return new URL(NO.canon(new URL(String(u), cur.href).href)); }
32
+ function live() { return NO.seq === SEQ; }
33
+ function report(kind, detail) { if (live()) NO.report(kind, detail); }
34
+
35
+ // --- location ------------------------------------------------------------------
36
+ function scrollToHash() {
37
+ if (!cur.hash || cur.hash === "#") return;
38
+ var id = decodeURIComponent(cur.hash.slice(1));
39
+ var el = D.getElementById(id) || D.getElementsByName(id)[0];
40
+ if (el && el.scrollIntoView) el.scrollIntoView();
41
+ }
42
+ function go(v, replace) {
43
+ var u;
44
+ try { u = abs(v); } catch (e) { return; }
45
+ if (u.origin === cur.origin && u.pathname === cur.pathname && u.search === cur.search && u.hash) {
46
+ var old = cur.href;
47
+ cur = u;
48
+ if (live()) NO.frameHistory(u.href, replace ? "replace" : "push");
49
+ scrollToHash();
50
+ try { W.dispatchEvent(new W.HashChangeEvent("hashchange", { oldURL: old, newURL: u.href })); } catch (e) {}
51
+ return;
52
+ }
53
+ if (live()) NO.navigate(u.href, { replace: !!replace });
54
+ }
55
+ function edit(part) {
56
+ return function (v) { var u = new URL(cur.href); u[part] = v; go(u.href); };
57
+ }
58
+ var loc = {
59
+ get href() { return cur.href; }, set href(v) { go(v); },
60
+ get origin() { return cur.origin; },
61
+ get protocol() { return cur.protocol; }, set protocol(v) {},
62
+ get host() { return cur.host; }, set host(v) {},
63
+ get hostname() { return cur.hostname; }, set hostname(v) {},
64
+ get port() { return cur.port; }, set port(v) {},
65
+ get pathname() { return cur.pathname; }, set pathname(v) { edit("pathname")(v); },
66
+ get search() { return cur.search; }, set search(v) { edit("search")(v); },
67
+ get hash() { return cur.hash; }, set hash(v) { edit("hash")(v); },
68
+ get ancestorOrigins() { return W.location.ancestorOrigins; },
69
+ assign: function (v) { go(v); },
70
+ replace: function (v) { go(v, true); },
71
+ reload: function () { if (live()) NO.reload(); },
72
+ toString: function () { return cur.href; },
73
+ };
74
+ // An accessor, so `location = "/x"` (rewritten to `__NOloc = "/x"`) navigates
75
+ // rather than replacing the object.
76
+ Object.defineProperty(W, "__NOloc", { get: function () { return loc; }, set: function (v) { go(v); } });
77
+
78
+ // --- history -------------------------------------------------------------------------
79
+ var HP = W.History.prototype, realReplace = HP.replaceState;
80
+ function hist(mode) {
81
+ return function (state, title, url) {
82
+ if (url !== undefined && url !== null) {
83
+ var u = abs(url);
84
+ if (u.origin !== cur.origin) throw new W.DOMException("Failed to execute '" + mode + "State' on 'History': cross-origin URL", "SecurityError");
85
+ cur = u;
86
+ if (live()) NO.frameHistory(u.href, mode);
87
+ }
88
+ // Never pass the URL on: the real document is about:srcdoc and would throw.
89
+ return realReplace.call(this, state, title === undefined ? "" : title);
90
+ };
91
+ }
92
+ HP.pushState = hist("push");
93
+ HP.replaceState = hist("replace");
94
+
95
+ // --- URL mapping ---------------------------------------------------------------------
96
+ function remap(v) {
97
+ if (v == null || v === "") return v;
98
+ var s = String(v);
99
+ if (/^(?:blob:|data:|javascript:|about:|#)/i.test(s)) return v;
100
+ var u;
101
+ try { u = new URL(s, D.baseURI); } catch (e) { return v; }
102
+ return NO.assetURLFor(u.href) || v;
103
+ }
104
+ function remapSrcset(v) {
105
+ if (v == null) return v;
106
+ var out = [];
107
+ String(v).split(/\s*,\s+/).forEach(function (part) {
108
+ var bits = part.trim().split(/\s+/);
109
+ if (!bits[0]) return;
110
+ var r = remap(bits[0]);
111
+ if (r !== bits[0] || /^(?:blob:|data:)/.test(bits[0])) { bits[0] = r; out.push(bits.join(" ")); }
112
+ });
113
+ return out.join(", ");
114
+ }
115
+ function unmap(v) {
116
+ if (typeof v === "string" && v.lastIndexOf("blob:", 0) === 0) {
117
+ var k = NO.unmap(v);
118
+ if (k != null) return k;
119
+ }
120
+ return v;
121
+ }
122
+ function unmapAbs(v) {
123
+ var k = unmap(v);
124
+ return k === v ? v : k.charAt(0) === "/" ? cur.origin + k : k;
125
+ }
126
+ function patch(Ctor, prop, fn, list) {
127
+ if (!Ctor) return;
128
+ var proto = Ctor.prototype, d = Object.getOwnPropertyDescriptor(proto, prop);
129
+ if (!d || !d.set || !d.get) return;
130
+ Object.defineProperty(proto, prop, {
131
+ configurable: true,
132
+ enumerable: d.enumerable,
133
+ get: function () { var val = d.get.call(this); return list ? val : unmapAbs(val); },
134
+ set: function (v) { d.set.call(this, fn(v)); },
135
+ });
136
+ }
137
+ patch(W.HTMLScriptElement, "src", remap);
138
+ patch(W.HTMLLinkElement, "href", remap);
139
+ patch(W.HTMLLinkElement, "imageSrcset", remapSrcset, true);
140
+ patch(W.HTMLImageElement, "src", remap);
141
+ patch(W.HTMLImageElement, "srcset", remapSrcset, true);
142
+ patch(W.HTMLSourceElement, "src", remap);
143
+ patch(W.HTMLSourceElement, "srcset", remapSrcset, true);
144
+ patch(W.HTMLMediaElement, "src", remap);
145
+ patch(W.HTMLVideoElement, "poster", remap);
146
+ patch(W.HTMLInputElement, "src", remap);
147
+ patch(W.HTMLEmbedElement, "src", remap);
148
+ patch(W.HTMLTrackElement, "src", remap);
149
+
150
+ var URL_ATTRS = { src: remap, href: remap, poster: remap, "xlink:href": remap, srcset: remapSrcset, imagesrcset: remapSrcset };
151
+ var NOT_ASSETS = { a: 1, area: 1, form: 1, base: 1, iframe: 1 };
152
+ EP.setAttribute = function (name, value) {
153
+ var f = URL_ATTRS[String(name).toLowerCase()];
154
+ if (f && !NOT_ASSETS[this.localName]) value = f(value);
155
+ return sa.call(this, name, value);
156
+ };
157
+ EP.setAttributeNS = function (ns, name, value) {
158
+ var n = String(name).toLowerCase(), f = URL_ATTRS[n] || (n === "href" ? remap : null);
159
+ if (f && !NOT_ASSETS[this.localName]) value = f(value);
160
+ return san.call(this, ns, name, value);
161
+ };
162
+ EP.getAttribute = function (name) {
163
+ var v = ga.call(this, name);
164
+ return v && v.charCodeAt(0) === 98 ? unmap(v) : v;
165
+ };
166
+
167
+ // `script[src="/_next/…/x.js"]` must still find a script whose attribute is
168
+ // now a blob: URL — chunk loaders dedupe this way.
169
+ function fixSel(sel) {
170
+ if (typeof sel !== "string" || sel.indexOf("[") < 0) return sel;
171
+ return sel.replace(/\[\s*(src|href)\s*(\^?=)\s*(["'])((?:\\.|(?!\3)[^\\])*)\3\s*\]/g, function (m, at, op, q, val) {
172
+ var raw = val.replace(/\\(.)/g, "$1"), clean = op === "^=" ? raw.replace(/[?#]$/, "") : raw, b = null;
173
+ try { b = NO.assetURLFor(new URL(clean, D.baseURI).href); } catch (e) {}
174
+ return b ? ":is(" + m + ",[" + at + op + '"' + b + '"])' : m;
175
+ });
176
+ }
177
+ [W.Document, W.Element, W.DocumentFragment].forEach(function (C) {
178
+ if (!C) return;
179
+ ["querySelector", "querySelectorAll"].forEach(function (fn) {
180
+ var orig = C.prototype[fn];
181
+ if (!orig) return;
182
+ C.prototype[fn] = function (sel) { return orig.call(this, fixSel(sel)); };
183
+ });
184
+ });
185
+ ["matches", "closest"].forEach(function (fn) {
186
+ var orig = EP[fn];
187
+ if (orig) EP[fn] = function (sel) { return orig.call(this, fixSel(sel)); };
188
+ });
189
+
190
+ // --- fetch & XHR -----------------------------------------------------------------------
191
+ var realFetch = W.fetch, R = W.Response, WP = W.Promise;
192
+ function mk(body, status, type, url) {
193
+ var nullBody = status === 101 || status === 204 || status === 205 || status === 304;
194
+ var r = new R(nullBody ? null : body, { status: status || 200, headers: { "content-type": type } });
195
+ try { Object.defineProperty(r, "url", { value: url }); } catch (e) {}
196
+ return r;
197
+ }
198
+ function isRSC(req, u) {
199
+ var h = req.headers;
200
+ return h.get("rsc") === "1" || h.has("next-router-state-tree") || h.has("next-router-prefetch") ||
201
+ h.has("next-router-segment-prefetch") || u.searchParams.has("_rsc");
202
+ }
203
+ function fdToObj(fd) {
204
+ var o = {};
205
+ fd.forEach(function (v, k) { o[k] = typeof v === "string" ? v : (v && v.name) || ""; });
206
+ return o;
207
+ }
208
+ function readFields(req) {
209
+ var ct = req.headers.get("content-type") || "";
210
+ var p = /form/.test(ct) ? req.formData().then(fdToObj)
211
+ : /json/.test(ct) ? req.json()
212
+ : req.text().then(function (t) { return t ? { body: t } : {}; });
213
+ return WP.resolve(p).catch(function () { return {}; });
214
+ }
215
+ W.fetch = function (input, init) {
216
+ var req;
217
+ try { req = new W.Request(input, init); } catch (e) { return WP.reject(e); }
218
+ var u = new URL(req.url);
219
+ if (u.protocol === "blob:" || u.protocol === "data:") return realFetch.apply(W, arguments);
220
+ var m = req.method.toUpperCase();
221
+ if (m === "GET" || m === "HEAD") {
222
+ if (u.origin === cur.origin && isRSC(req, u)) return WP.resolve(mk("", 200, "text/html; charset=utf-8", u.href));
223
+ return WP.resolve(NO.fetch(u.href)).then(function (hit) {
224
+ if (!hit) {
225
+ report("miss", m + " " + u.href);
226
+ return mk(JSON.stringify({ error: "Not included in this offline snapshot." }), 404, "application/json", u.href);
227
+ }
228
+ return mk(m === "HEAD" ? null : hit.bytes, hit.status, hit.type, u.href);
229
+ });
230
+ }
231
+ return readFields(req).then(function (fields) {
232
+ if (NO.write(u.href, m, fields)) return mk(JSON.stringify({ ok: true, offline: true }), 200, "application/json", u.href);
233
+ return mk(JSON.stringify({ error: "This is a read-only offline snapshot; changes cannot be saved." }), 503, "application/json", u.href);
234
+ });
235
+ };
236
+
237
+ // Enough of XMLHttpRequest for axios and friends, routed through fetch above.
238
+ class OfflineXHR extends W.EventTarget {
239
+ constructor() {
240
+ super();
241
+ this.readyState = 0; this.status = 0; this.statusText = ""; this.response = null; this.responseText = "";
242
+ this.responseType = ""; this.responseURL = ""; this.timeout = 0; this.withCredentials = false;
243
+ this.upload = new W.EventTarget(); this._h = {}; this._rh = {};
244
+ }
245
+ open(method, url) { this._m = String(method).toUpperCase(); this._u = abs(url).href; this._aborted = false; this._set(1); }
246
+ setRequestHeader(k, v) { this._h[k] = v; }
247
+ getResponseHeader(k) { var v = this._rh[String(k).toLowerCase()]; return v == null ? null : v; }
248
+ getAllResponseHeaders() { var s = ""; for (var k in this._rh) s += k + ": " + this._rh[k] + "\r\n"; return s; }
249
+ overrideMimeType() {}
250
+ abort() { this._aborted = true; this._set(4); this._fire("abort"); this._fire("loadend"); }
251
+ send(body) {
252
+ var self = this, noBody = this._m === "GET" || this._m === "HEAD";
253
+ W.fetch(this._u, { method: this._m, headers: this._h, body: noBody ? undefined : body }).then(function (r) {
254
+ if (self._aborted) return;
255
+ self.status = r.status; self.statusText = r.statusText; self.responseURL = self._u;
256
+ r.headers.forEach(function (v, k) { self._rh[k] = v; });
257
+ self._set(2);
258
+ var rt = self.responseType;
259
+ return (rt === "arraybuffer" ? r.arrayBuffer() : rt === "blob" ? r.blob() : r.text()).then(function (data) {
260
+ if (self._aborted) return;
261
+ if (rt === "" || rt === "text") { self.responseText = data; self.response = data; }
262
+ else if (rt === "json") { try { self.response = JSON.parse(data); } catch (e) { self.response = null; } }
263
+ else self.response = data;
264
+ self._set(3); self._set(4); self._fire("load"); self._fire("loadend");
265
+ });
266
+ }, function () {
267
+ if (self._aborted) return;
268
+ self._set(4); self._fire("error"); self._fire("loadend");
269
+ });
270
+ }
271
+ _set(s) { this.readyState = s; this._fire("readystatechange"); }
272
+ _fire(type) {
273
+ var e = new W.Event(type);
274
+ this.dispatchEvent(e);
275
+ var h = this["on" + type];
276
+ if (typeof h === "function") h.call(this, e);
277
+ }
278
+ }
279
+ ["UNSENT", "OPENED", "HEADERS_RECEIVED", "LOADING", "DONE"].forEach(function (n, i) { OfflineXHR[n] = i; OfflineXHR.prototype[n] = i; });
280
+ W.XMLHttpRequest = OfflineXHR;
281
+ if (W.navigator.sendBeacon) W.navigator.sendBeacon = function () { return true; };
282
+
283
+ // --- windows, cookies ---------------------------------------------------------------------
284
+ var realOpen = W.open;
285
+ W.open = function (url, target, features) {
286
+ if (url == null || url === "") return realOpen.apply(W, arguments);
287
+ var u;
288
+ try { u = abs(url); } catch (e) { return null; }
289
+ if (u.origin === cur.origin) {
290
+ if (target === "_self" || target === "_top" || target === "_parent") { go(u.href); return W; }
291
+ NO.openInNewTab(u.href);
292
+ return null;
293
+ }
294
+ return realOpen.call(W, u.href, target || "_blank", features);
295
+ };
296
+ var jar = NO.cookies;
297
+ try {
298
+ Object.defineProperty(D, "cookie", {
299
+ configurable: true,
300
+ get: function () { return Object.keys(jar).map(function (k) { return k + "=" + jar[k]; }).join("; "); },
301
+ set: function (v) {
302
+ var s = String(v), kv = s.split(";")[0], i = kv.indexOf("=");
303
+ if (i < 0) return;
304
+ var k = kv.slice(0, i).trim(), exp = /;\s*expires\s*=\s*([^;]+)/i.exec(s);
305
+ if (/;\s*max-age\s*=\s*(-\d+|0)\s*(;|$)/i.test(s) || (exp && Date.parse(exp[1]) < Date.now())) delete jar[k];
306
+ else jar[k] = kv.slice(i + 1).trim();
307
+ },
308
+ });
309
+ } catch (e) {}
310
+
311
+ // --- links and forms ------------------------------------------------------------------------
312
+ // Bubble phase on window: runs after the app's own handlers, so a link the app
313
+ // already handled (Next's <Link> calls preventDefault) is left alone.
314
+ function onLink(e, newTab) {
315
+ if (e.defaultPrevented) return;
316
+ var a = e.target && e.target.closest ? e.target.closest("a[href], area[href]") : null;
317
+ if (!a) return;
318
+ var href = ga.call(a, "href");
319
+ if (href == null || /^javascript:/i.test(href)) return;
320
+ var u;
321
+ try { u = new URL(href, D.baseURI); } catch (x) { return; }
322
+ if (!/^https?:$/.test(u.protocol)) return;
323
+ e.preventDefault();
324
+ if (a.hasAttribute("download") || NO.isAsset(u.href)) { NO.openAsset(u.href, a.getAttribute("download")); return; }
325
+ if (u.origin !== cur.origin) { realOpen.call(W, u.href, "_blank", "noopener"); return; }
326
+ var t = a.getAttribute("target");
327
+ if (newTab || e.metaKey || e.ctrlKey || e.shiftKey || (t && !/^_(self|top|parent)$/i.test(t))) { NO.openInNewTab(u.href); return; }
328
+ go(u.href);
329
+ }
330
+ W.addEventListener("click", function (e) { if (e.button === 0) onLink(e, false); });
331
+ W.addEventListener("auxclick", function (e) { if (e.button === 1) onLink(e, true); });
332
+ W.addEventListener("submit", function (e) {
333
+ if (e.defaultPrevented) return;
334
+ var f = e.target, s = e.submitter;
335
+ var method = ((s && s.getAttribute("formmethod")) || f.getAttribute("method") || "get").toLowerCase();
336
+ var action = (s && s.getAttribute("formaction")) || f.getAttribute("action") || cur.href;
337
+ var u, fd;
338
+ try { u = new URL(action, D.baseURI); } catch (x) { return; }
339
+ try { fd = new W.FormData(f, s); } catch (x) { fd = new W.FormData(f); }
340
+ e.preventDefault();
341
+ if (method === "get") { u.search = new W.URLSearchParams(fd).toString(); go(u.href); return; }
342
+ NO.submit(u.href, method, fdToObj(fd));
343
+ });
344
+
345
+ // --- links to pages the snapshot does not hold -----------------------------------------------
346
+ // Marked with data-no-missing; the bundle's per-page CSS hides or dims them.
347
+ // Observed from the first parsed node, so a hidden link never flashes.
348
+ if (NO.manifest.missingLinks && NO.manifest.missingLinks !== "show") {
349
+ var checkLink = function (a) {
350
+ var href = ga.call(a, "href");
351
+ if (!href || href.charAt(0) === "#" || /^(javascript|mailto|tel):/i.test(href)) return;
352
+ var u;
353
+ try { u = abs(href); } catch (e) { return; }
354
+ if (u.origin !== cur.origin) return;
355
+ if (NO.hasPage(u.href) || NO.isAsset(u.href)) { if (a.hasAttribute("data-no-missing")) a.removeAttribute("data-no-missing"); }
356
+ else if (!a.hasAttribute("data-no-missing")) sa.call(a, "data-no-missing", "");
357
+ };
358
+ var scan = function (node) {
359
+ if (node.nodeType !== 1) return;
360
+ if (node.localName === "a" || node.localName === "area") checkLink(node);
361
+ var list = node.getElementsByTagName("a");
362
+ for (var i = 0; i < list.length; i++) checkLink(list[i]);
363
+ };
364
+ new W.MutationObserver(function (muts) {
365
+ for (var i = 0; i < muts.length; i++) {
366
+ var m = muts[i];
367
+ if (m.type === "attributes") { if (m.target.localName === "a" || m.target.localName === "area") checkLink(m.target); continue; }
368
+ for (var j = 0; j < m.addedNodes.length; j++) scan(m.addedNodes[j]);
369
+ }
370
+ }).observe(D, { subtree: true, childList: true, attributes: true, attributeFilter: ["href"] });
371
+ }
372
+
373
+ // --- diagnostics ----------------------------------------------------------------------------
374
+ W.addEventListener("error", function (e) {
375
+ var t = e.target;
376
+ if (t && t !== W && t.nodeType === 1) {
377
+ report("resource", (t.localName || "") + " " + unmap(ga.call(t, "src") || ga.call(t, "href") || ""));
378
+ return;
379
+ }
380
+ report("error", (e.message || "error") + (e.filename ? " @ " + unmap(e.filename) + ":" + e.lineno : ""));
381
+ }, true);
382
+ W.addEventListener("unhandledrejection", function (e) {
383
+ var r = e.reason;
384
+ report("rejection", (r && (r.stack || r.message)) || String(r));
385
+ });
386
+ D.addEventListener("securitypolicyviolation", function (e) {
387
+ report("blocked", e.violatedDirective + " " + e.blockedURI);
388
+ });
389
+
390
+ // --- lifecycle --------------------------------------------------------------------------------
391
+ D.addEventListener("DOMContentLoaded", function () {
392
+ NO.frameReady(W, SEQ);
393
+ var meta = D.querySelector("meta[data-no-refresh]");
394
+ if (meta) {
395
+ var m = /^\s*(\d+)?\s*[;,]?\s*(?:url\s*=\s*)?(.*)$/i.exec(meta.getAttribute("content") || "");
396
+ if (m && m[2]) setTimeout(function () { go(m[2].replace(/^['"]|['"]$/g, ""), true); }, (+m[1] || 0) * 1000);
397
+ }
398
+ if (D.head && W.MutationObserver) {
399
+ new W.MutationObserver(function () { NO.title(W, D.title); })
400
+ .observe(D.head, { subtree: true, childList: true, characterData: true });
401
+ }
402
+ });
403
+ W.addEventListener("load", scrollToHash);
404
+ })();