@aglyn/aglyn 1.0.0-beta.244 → 1.0.0-beta.247
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.
- package/package.json +11 -11
- package/src/lib/app-utils/browser-family.d.ts +49 -0
- package/src/lib/app-utils/browser-family.js +78 -0
- package/src/lib/app-utils/browser-family.js.map +1 -0
- package/src/lib/app-utils/contact-custom-fields.d.ts +23 -1
- package/src/lib/app-utils/contact-custom-fields.js +84 -2
- package/src/lib/app-utils/contact-custom-fields.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +7 -1
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/default-site.js +21 -33
- package/src/lib/app-utils/default-site.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +14 -13
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +13 -13
- package/src/lib/app-utils/docs-help.generated.js +17 -6
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +238 -56
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/element-pick-protocol.d.ts +54 -0
- package/src/lib/app-utils/element-pick-protocol.js +41 -0
- package/src/lib/app-utils/element-pick-protocol.js.map +1 -0
- package/src/lib/app-utils/element-pick-target.d.ts +32 -0
- package/src/lib/app-utils/element-pick-target.js +47 -0
- package/src/lib/app-utils/element-pick-target.js.map +1 -0
- package/src/lib/app-utils/error-beacon.d.ts +115 -4
- package/src/lib/app-utils/error-beacon.js +180 -11
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/form-contract.js +13 -6
- package/src/lib/app-utils/form-contract.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +31 -0
- package/src/lib/app-utils/forms.js +213 -16
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +13 -0
- package/src/lib/app-utils/health-report.js +24 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/media-takedown-reach.d.ts +12 -6
- package/src/lib/app-utils/media-takedown-reach.js +11 -6
- package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.js +12 -0
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/seo-locale.d.ts +14 -0
- package/src/lib/app-utils/seo-locale.js +15 -0
- package/src/lib/app-utils/seo-locale.js.map +1 -1
- package/src/lib/app-utils/seo-site-scan.js +18 -3
- package/src/lib/app-utils/seo-site-scan.js.map +1 -1
- package/src/lib/app-utils/site-live-url.d.ts +24 -0
- package/src/lib/app-utils/site-live-url.js +60 -0
- package/src/lib/app-utils/site-live-url.js.map +1 -0
- package/src/lib/app-utils/starter-template-nodes.d.ts +13 -0
- package/src/lib/app-utils/starter-template-nodes.js +40 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +1 -1
- package/src/lib/app-utils/starter-templates.js +19 -31
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +74 -3
- package/src/lib/plugin-manager/feature-plugins.js +9 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +18 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -1
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +23 -0
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
- package/src/lib/plugin-manager/stock-photo-provider.d.ts +67 -0
- package/src/lib/plugin-manager/stock-photo-provider.js +14 -1
- package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The live-page element picker's wire protocol (AGL-3758).
|
|
19
|
+
*
|
|
20
|
+
* The console's Actions card opens a site page in a frame with
|
|
21
|
+
* {@link ELEMENT_PICK_PARAM}; the page's pick mode highlights elements under
|
|
22
|
+
* the pointer and, on a click, posts the element's selector to the console —
|
|
23
|
+
* the same `[data-aglyn="leaf:<id>"]` selector the Besigner's Interactions
|
|
24
|
+
* "Pick element" stores, which the site runtime resolves through
|
|
25
|
+
* `expandLeafSelector`. Import-free on purpose: the tenant's admin-bar stub,
|
|
26
|
+
* on every published page, reads the param from here.
|
|
27
|
+
*/
|
|
28
|
+
/** Query param that arms pick mode on a framed published page. */
|
|
29
|
+
export declare const ELEMENT_PICK_PARAM = "aglyn-pick";
|
|
30
|
+
/** `postMessage` type every pick-mode message carries. */
|
|
31
|
+
export declare const ELEMENT_PICK_MESSAGE_TYPE = "aglyn-pick-element";
|
|
32
|
+
export type ElementPickMessage =
|
|
33
|
+
/** Pick mode loaded and is waiting for a click. */
|
|
34
|
+
{
|
|
35
|
+
type: typeof ELEMENT_PICK_MESSAGE_TYPE;
|
|
36
|
+
event: 'ready';
|
|
37
|
+
}
|
|
38
|
+
/** The builder clicked an element. */
|
|
39
|
+
| {
|
|
40
|
+
type: typeof ELEMENT_PICK_MESSAGE_TYPE;
|
|
41
|
+
event: 'picked';
|
|
42
|
+
selector: string;
|
|
43
|
+
/** What the element reads as, for the console's chip. */
|
|
44
|
+
label: string;
|
|
45
|
+
}
|
|
46
|
+
/** Escape on the page. */
|
|
47
|
+
| {
|
|
48
|
+
type: typeof ELEMENT_PICK_MESSAGE_TYPE;
|
|
49
|
+
event: 'cancel';
|
|
50
|
+
};
|
|
51
|
+
/** Whether a `message` event's data is one of ours, shaped as promised. */
|
|
52
|
+
export declare function isElementPickMessage(data: unknown): data is ElementPickMessage;
|
|
53
|
+
/** `pageUrl` with pick mode armed, keeping any query it already has. */
|
|
54
|
+
export declare function elementPickUrl(pageUrl: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The live-page element picker's wire protocol (AGL-3758).
|
|
18
|
+
*
|
|
19
|
+
* The console's Actions card opens a site page in a frame with
|
|
20
|
+
* {@link ELEMENT_PICK_PARAM}; the page's pick mode highlights elements under
|
|
21
|
+
* the pointer and, on a click, posts the element's selector to the console —
|
|
22
|
+
* the same `[data-aglyn="leaf:<id>"]` selector the Besigner's Interactions
|
|
23
|
+
* "Pick element" stores, which the site runtime resolves through
|
|
24
|
+
* `expandLeafSelector`. Import-free on purpose: the tenant's admin-bar stub,
|
|
25
|
+
* on every published page, reads the param from here.
|
|
26
|
+
*/ /** Query param that arms pick mode on a framed published page. */ export const ELEMENT_PICK_PARAM = 'aglyn-pick';
|
|
27
|
+
/** `postMessage` type every pick-mode message carries. */ export const ELEMENT_PICK_MESSAGE_TYPE = 'aglyn-pick-element';
|
|
28
|
+
/** Whether a `message` event's data is one of ours, shaped as promised. */ export function isElementPickMessage(data) {
|
|
29
|
+
if (!data || typeof data !== 'object') return false;
|
|
30
|
+
const message = data;
|
|
31
|
+
if (message['type'] !== ELEMENT_PICK_MESSAGE_TYPE) return false;
|
|
32
|
+
if (message['event'] === 'ready' || message['event'] === 'cancel') return true;
|
|
33
|
+
return message['event'] === 'picked' && typeof message['selector'] === 'string' && message['selector'] !== '' && typeof message['label'] === 'string';
|
|
34
|
+
}
|
|
35
|
+
/** `pageUrl` with pick mode armed, keeping any query it already has. */ export function elementPickUrl(pageUrl) {
|
|
36
|
+
const url = new URL(pageUrl);
|
|
37
|
+
url.searchParams.set(ELEMENT_PICK_PARAM, '1');
|
|
38
|
+
return url.toString();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=element-pick-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/element-pick-protocol.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The live-page element picker's wire protocol (AGL-3758).\n *\n * The console's Actions card opens a site page in a frame with\n * {@link ELEMENT_PICK_PARAM}; the page's pick mode highlights elements under\n * the pointer and, on a click, posts the element's selector to the console —\n * the same `[data-aglyn=\"leaf:<id>\"]` selector the Besigner's Interactions\n * \"Pick element\" stores, which the site runtime resolves through\n * `expandLeafSelector`. Import-free on purpose: the tenant's admin-bar stub,\n * on every published page, reads the param from here.\n */\n\n/** Query param that arms pick mode on a framed published page. */\nexport const ELEMENT_PICK_PARAM = 'aglyn-pick'\n\n/** `postMessage` type every pick-mode message carries. */\nexport const ELEMENT_PICK_MESSAGE_TYPE = 'aglyn-pick-element'\n\nexport type ElementPickMessage =\n /** Pick mode loaded and is waiting for a click. */\n | { type: typeof ELEMENT_PICK_MESSAGE_TYPE; event: 'ready' }\n /** The builder clicked an element. */\n | {\n type: typeof ELEMENT_PICK_MESSAGE_TYPE\n event: 'picked'\n selector: string\n /** What the element reads as, for the console's chip. */\n label: string\n }\n /** Escape on the page. */\n | { type: typeof ELEMENT_PICK_MESSAGE_TYPE; event: 'cancel' }\n\n/** Whether a `message` event's data is one of ours, shaped as promised. */\nexport function isElementPickMessage(data: unknown): data is ElementPickMessage {\n if (!data || typeof data !== 'object') return false\n const message = data as Record<string, unknown>\n if (message['type'] !== ELEMENT_PICK_MESSAGE_TYPE) return false\n if (message['event'] === 'ready' || message['event'] === 'cancel') return true\n return (\n message['event'] === 'picked' &&\n typeof message['selector'] === 'string' &&\n message['selector'] !== '' &&\n typeof message['label'] === 'string'\n )\n}\n\n/** `pageUrl` with pick mode armed, keeping any query it already has. */\nexport function elementPickUrl(pageUrl: string): string {\n const url = new URL(pageUrl)\n url.searchParams.set(ELEMENT_PICK_PARAM, '1')\n return url.toString()\n}\n"],"names":["ELEMENT_PICK_PARAM","ELEMENT_PICK_MESSAGE_TYPE","isElementPickMessage","data","message","elementPickUrl","pageUrl","url","URL","searchParams","set","toString"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GAED,gEAAgE,GAChE,OAAO,MAAMA,qBAAqB,aAAY;AAE9C,wDAAwD,GACxD,OAAO,MAAMC,4BAA4B,qBAAoB;AAgB7D,yEAAyE,GACzE,OAAO,SAASC,qBAAqBC,IAAa;IAChD,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU,OAAO;IAC9C,MAAMC,UAAUD;IAChB,IAAIC,OAAO,CAAC,OAAO,KAAKH,2BAA2B,OAAO;IAC1D,IAAIG,OAAO,CAAC,QAAQ,KAAK,WAAWA,OAAO,CAAC,QAAQ,KAAK,UAAU,OAAO;IAC1E,OACEA,OAAO,CAAC,QAAQ,KAAK,YACrB,OAAOA,OAAO,CAAC,WAAW,KAAK,YAC/BA,OAAO,CAAC,WAAW,KAAK,MACxB,OAAOA,OAAO,CAAC,QAAQ,KAAK;AAEhC;AAEA,sEAAsE,GACtE,OAAO,SAASC,eAAeC,OAAe;IAC5C,MAAMC,MAAM,IAAIC,IAAIF;IACpBC,IAAIE,YAAY,CAACC,GAAG,CAACV,oBAAoB;IACzC,OAAOO,IAAII,QAAQ;AACrB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
export interface ElementPickTarget {
|
|
18
|
+
/** The stamped element the click resolves to. */
|
|
19
|
+
element: Element;
|
|
20
|
+
/** The selector stored for it — the Besigner picker's form. */
|
|
21
|
+
selector: string;
|
|
22
|
+
/** What it reads as: its accessible name or text, else its tag. */
|
|
23
|
+
label: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The element a pick resolves to (AGL-3758): the nearest ancestor-or-self the
|
|
27
|
+
* renderer stamped, its stored selector, and a label. A layout's namespace is
|
|
28
|
+
* stripped off the id, exactly as the Besigner picker stores the raw canvas
|
|
29
|
+
* id; the runtime's `expandLeafSelector` matches it on every page again.
|
|
30
|
+
* `null` for a point on nothing Aglyn drew.
|
|
31
|
+
*/
|
|
32
|
+
export declare function elementPickTarget(from: Element | null): ElementPickTarget | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { normalizeLeafId } from "./element-ui.js";
|
|
17
|
+
/** The stamp the node renderer's `Leaf` puts on every element it draws. */ const LEAF_STAMP_SELECTOR = '[data-aglyn^="leaf:"]';
|
|
18
|
+
function readableLabel(element) {
|
|
19
|
+
var _element_textContent;
|
|
20
|
+
const named = element.getAttribute('aria-label') || element.getAttribute('alt') || element.getAttribute('title') || ((_element_textContent = element.textContent) != null ? _element_textContent : '');
|
|
21
|
+
const text = named.replace(/\s+/g, ' ').trim();
|
|
22
|
+
const tag = element.tagName.toLowerCase();
|
|
23
|
+
if (!text) return tag === 'img' ? 'Image' : `Element (${tag})`;
|
|
24
|
+
return text.length > 40 ? `“${text.slice(0, 39)}…”` : `“${text}”`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The element a pick resolves to (AGL-3758): the nearest ancestor-or-self the
|
|
28
|
+
* renderer stamped, its stored selector, and a label. A layout's namespace is
|
|
29
|
+
* stripped off the id, exactly as the Besigner picker stores the raw canvas
|
|
30
|
+
* id; the runtime's `expandLeafSelector` matches it on every page again.
|
|
31
|
+
* `null` for a point on nothing Aglyn drew.
|
|
32
|
+
*/ export function elementPickTarget(from) {
|
|
33
|
+
var _ref, _element_getAttribute;
|
|
34
|
+
var _from_closest;
|
|
35
|
+
const element = (_ref = from == null ? void 0 : (_from_closest = from.closest) == null ? void 0 : _from_closest.call(from, LEAF_STAMP_SELECTOR)) != null ? _ref : null;
|
|
36
|
+
if (!element) return null;
|
|
37
|
+
const stamp = (_element_getAttribute = element.getAttribute('data-aglyn')) != null ? _element_getAttribute : '';
|
|
38
|
+
const id = normalizeLeafId(stamp.slice('leaf:'.length));
|
|
39
|
+
if (!id || id.includes('"') || id.includes('\\')) return null;
|
|
40
|
+
return {
|
|
41
|
+
element,
|
|
42
|
+
selector: `[data-aglyn="leaf:${id}"]`,
|
|
43
|
+
label: readableLabel(element)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=element-pick-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/element-pick-target.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { normalizeLeafId } from './element-ui'\n\n/** The stamp the node renderer's `Leaf` puts on every element it draws. */\nconst LEAF_STAMP_SELECTOR = '[data-aglyn^=\"leaf:\"]'\n\nexport interface ElementPickTarget {\n /** The stamped element the click resolves to. */\n element: Element\n /** The selector stored for it — the Besigner picker's form. */\n selector: string\n /** What it reads as: its accessible name or text, else its tag. */\n label: string\n}\n\nfunction readableLabel(element: Element): string {\n const named =\n element.getAttribute('aria-label') ||\n element.getAttribute('alt') ||\n element.getAttribute('title') ||\n (element.textContent ?? '')\n const text = named.replace(/\\s+/g, ' ').trim()\n const tag = element.tagName.toLowerCase()\n if (!text) return tag === 'img' ? 'Image' : `Element (${tag})`\n return text.length > 40 ? `“${text.slice(0, 39)}…”` : `“${text}”`\n}\n\n/**\n * The element a pick resolves to (AGL-3758): the nearest ancestor-or-self the\n * renderer stamped, its stored selector, and a label. A layout's namespace is\n * stripped off the id, exactly as the Besigner picker stores the raw canvas\n * id; the runtime's `expandLeafSelector` matches it on every page again.\n * `null` for a point on nothing Aglyn drew.\n */\nexport function elementPickTarget(from: Element | null): ElementPickTarget | null {\n const element = from?.closest?.(LEAF_STAMP_SELECTOR) ?? null\n if (!element) return null\n const stamp = element.getAttribute('data-aglyn') ?? ''\n const id = normalizeLeafId(stamp.slice('leaf:'.length))\n if (!id || id.includes('\"') || id.includes('\\\\')) return null\n return {\n element,\n selector: `[data-aglyn=\"leaf:${id}\"]`,\n label: readableLabel(element),\n }\n}\n"],"names":["normalizeLeafId","LEAF_STAMP_SELECTOR","readableLabel","element","named","getAttribute","textContent","text","replace","trim","tag","tagName","toLowerCase","length","slice","elementPickTarget","from","closest","stamp","id","includes","selector","label"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,kBAAc;AAE9C,yEAAyE,GACzE,MAAMC,sBAAsB;AAW5B,SAASC,cAAcC,OAAgB;QAKlCA;IAJH,MAAMC,QACJD,QAAQE,YAAY,CAAC,iBACrBF,QAAQE,YAAY,CAAC,UACrBF,QAAQE,YAAY,CAAC,cACpBF,uBAAAA,QAAQG,WAAW,YAAnBH,uBAAuB;IAC1B,MAAMI,OAAOH,MAAMI,OAAO,CAAC,QAAQ,KAAKC,IAAI;IAC5C,MAAMC,MAAMP,QAAQQ,OAAO,CAACC,WAAW;IACvC,IAAI,CAACL,MAAM,OAAOG,QAAQ,QAAQ,UAAU,CAAC,SAAS,EAAEA,IAAI,CAAC,CAAC;IAC9D,OAAOH,KAAKM,MAAM,GAAG,KAAK,CAAC,CAAC,EAAEN,KAAKO,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAEP,KAAK,CAAC,CAAC;AACnE;AAEA;;;;;;CAMC,GACD,OAAO,SAASQ,kBAAkBC,IAAoB;cAGtCb;QAFEa;IAAhB,MAAMb,kBAAUa,yBAAAA,gBAAAA,KAAMC,OAAO,qBAAbD,mBAAAA,MAAgBf,uCAAwB;IACxD,IAAI,CAACE,SAAS,OAAO;IACrB,MAAMe,SAAQf,wBAAAA,QAAQE,YAAY,CAAC,yBAArBF,wBAAsC;IACpD,MAAMgB,KAAKnB,gBAAgBkB,MAAMJ,KAAK,CAAC,QAAQD,MAAM;IACrD,IAAI,CAACM,MAAMA,GAAGC,QAAQ,CAAC,QAAQD,GAAGC,QAAQ,CAAC,OAAO,OAAO;IACzD,OAAO;QACLjB;QACAkB,UAAU,CAAC,kBAAkB,EAAEF,GAAG,EAAE,CAAC;QACrCG,OAAOpB,cAAcC;IACvB;AACF"}
|
|
@@ -14,6 +14,36 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* First-party browser error beacon (AGL-1538).
|
|
19
|
+
*
|
|
20
|
+
* Crashlytics does not exist for web, and adding a third-party error vendor
|
|
21
|
+
* is a subprocessor-list decision that was explicitly deferred. This is the
|
|
22
|
+
* first-party alternative: `window.onerror` / `unhandledrejection` handlers
|
|
23
|
+
* that batch uncaught failures to the app's own `/api/errors` route, which
|
|
24
|
+
* forwards them to Google Cloud Error Reporting — a processor already on the
|
|
25
|
+
* list, alerting through the GCP stack that already pages (AGL-1502).
|
|
26
|
+
*
|
|
27
|
+
* Design constraints, in order:
|
|
28
|
+
*
|
|
29
|
+
* - **It must never become the outage.** Everything is wrapped; a failure to
|
|
30
|
+
* report is silence, not a thrown error. Batches are capped per page so an
|
|
31
|
+
* error thrown in a loop cannot DDoS our own API — after `maxPerPage`
|
|
32
|
+
* events the beacon disarms for the rest of the pageview.
|
|
33
|
+
* - **No PII.** URLs are scrubbed to origin + pathname (query strings and
|
|
34
|
+
* fragments carry tokens and search terms), no user id, no cookies —
|
|
35
|
+
* the payload is the error text and where in the CODE it happened.
|
|
36
|
+
* Cross-origin scripts surface as the browser's opaque "Script error."
|
|
37
|
+
* with no stack; those are dropped since they cannot be acted on.
|
|
38
|
+
* - **Dedupe.** One page throwing the same error on every frame reports it
|
|
39
|
+
* once.
|
|
40
|
+
*
|
|
41
|
+
* Framework-free on purpose: the console mounts it from its root layout, the
|
|
42
|
+
* tenant runtime from its own, and neither needs React here. Deliberately NO
|
|
43
|
+
* 'use client' directive — inside this shared lib the directive forks the
|
|
44
|
+
* module graph (AGL-52); every importer is already a client module.
|
|
45
|
+
*/
|
|
46
|
+
import { type BrowserFamily } from './browser-family';
|
|
17
47
|
export interface ErrorBeaconEvent {
|
|
18
48
|
/**
|
|
19
49
|
* Which handler caught it — 'error' | 'unhandledrejection' — or the
|
|
@@ -33,14 +63,21 @@ export interface ErrorBeaconEvent {
|
|
|
33
63
|
/** Page URL, scrubbed to origin + pathname. */
|
|
34
64
|
url: string;
|
|
35
65
|
/**
|
|
36
|
-
* On a `hydration` event only: the page
|
|
37
|
-
* visible in the document when React gave up
|
|
38
|
-
* {@link pageTranslationSignal}.
|
|
39
|
-
*
|
|
66
|
+
* On a `hydration` event from builds before AGL-3738 only: the page
|
|
67
|
+
* translator whose rewrite was visible in the document when React gave up
|
|
68
|
+
* — see {@link pageTranslationSignal}. Today's beacon drops a mismatch on a
|
|
69
|
+
* translated page instead of sending it, so the field stays only for what
|
|
70
|
+
* older tabs still report.
|
|
40
71
|
*/
|
|
41
72
|
translated?: string;
|
|
42
73
|
/** On a `hydration` event only: `navigator.language`, clamped. */
|
|
43
74
|
language?: string;
|
|
75
|
+
/**
|
|
76
|
+
* On every event: the coarse browser family — one of seven, see
|
|
77
|
+
* {@link browserFamily} (AGL-3732). Never the user agent, never a version.
|
|
78
|
+
* Stamped by the queue, so a caller never sets it.
|
|
79
|
+
*/
|
|
80
|
+
browser?: BrowserFamily;
|
|
44
81
|
}
|
|
45
82
|
export interface ErrorBeaconOptions {
|
|
46
83
|
/** POST target; same-origin. Default `/api/errors`. */
|
|
@@ -109,6 +146,50 @@ export declare function isInjectedThirdPartyFrame(stack: string, documentUrl: st
|
|
|
109
146
|
/** How many frames of this stack are in a script with no URL at all. */
|
|
110
147
|
export declare function anonymousScriptFrameCount(stack: string): number;
|
|
111
148
|
export declare function isBenignBrowserNotice(message: string): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Whether the document is a page a visitor navigated to over the web
|
|
151
|
+
* (AGL-3759).
|
|
152
|
+
*
|
|
153
|
+
* Only `http:` and `https:` qualify. A document at `about:blank` or
|
|
154
|
+
* `about:srcdoc` is a COPY of our page: a translator extension or a site that
|
|
155
|
+
* frames our markup renders it into a fresh document with no URL of its own,
|
|
156
|
+
* and our scripts run there too. Next.js's app router then throws
|
|
157
|
+
* `Failed to construct 'URL': Invalid URL` resolving its canonical URL against
|
|
158
|
+
* `window.location.href`, which no code of ours can prevent and no visitor on
|
|
159
|
+
* the real page ever sees. `file:` (a saved page) and an extension's own
|
|
160
|
+
* scheme are copies in the same sense.
|
|
161
|
+
*
|
|
162
|
+
* A missing or unreadable location is not a web document either: nothing
|
|
163
|
+
* from it can be traced to a page we serve.
|
|
164
|
+
*/
|
|
165
|
+
export declare function isWebDocument(location: {
|
|
166
|
+
protocol?: string;
|
|
167
|
+
} | null | undefined): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* A rejection that says only "this was cancelled" (AGL-3734).
|
|
170
|
+
*
|
|
171
|
+
* `AbortError` is what a request rejects with when its `AbortSignal` fires —
|
|
172
|
+
* a deadline we set, a component that unmounted, a search the author typed
|
|
173
|
+
* past — and what the browser rejects every in-flight fetch with when the
|
|
174
|
+
* document goes away under it. On 2026-10-10 one arrived from
|
|
175
|
+
* `/edit-access`, the page a published site opens in a popup or a hidden
|
|
176
|
+
* iframe and then CLOSES itself the moment it has its answer. The report had
|
|
177
|
+
* no stack and no message past the name, so there was no call site to fix:
|
|
178
|
+
* the page's own mint was already caught, and whatever else the console shell
|
|
179
|
+
* had in flight was cut off by the site ending the page, which is the flow
|
|
180
|
+
* working.
|
|
181
|
+
*
|
|
182
|
+
* Why the classifier rather than each call site: every request on every
|
|
183
|
+
* console page can be the one in flight when a page is torn down, and the
|
|
184
|
+
* abort is the intended outcome in every case. There is nothing a fix of ours
|
|
185
|
+
* could change, and nothing in the report to locate it by.
|
|
186
|
+
*
|
|
187
|
+
* Narrow on purpose. Only `name === 'AbortError'`, and only for an UNHANDLED
|
|
188
|
+
* REJECTION — not `TimeoutError`, which is `AbortSignal.timeout` saying a
|
|
189
|
+
* deadline was MISSED (a real slowness), and not an uncaught `error` event or
|
|
190
|
+
* a `reportHandledError` call, where somebody chose to report it.
|
|
191
|
+
*/
|
|
192
|
+
export declare function isAbortRejection(reason: unknown): boolean;
|
|
112
193
|
/**
|
|
113
194
|
* `kind` for Firebase's `auth/tenant-id-mismatch` — a sibling tab signed in to
|
|
114
195
|
* another account pool (AGL-3280). The console recovers the tab by reloading
|
|
@@ -164,6 +245,36 @@ export declare function isHydrationMismatch(message: string): boolean;
|
|
|
164
245
|
* Never throws: it runs inside the error handler.
|
|
165
246
|
*/
|
|
166
247
|
export declare function pageTranslationSignal(doc: Document, servedLang: string | null | undefined): string | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* A TRANSLATOR'S MARK, REMEMBERED FROM THE MOMENT IT APPEARS (AGL-3738).
|
|
250
|
+
*
|
|
251
|
+
* {@link pageTranslationSignal} read at the moment React reports a mismatch
|
|
252
|
+
* is reading a document the translator no longer owns. React reports a
|
|
253
|
+
* hydration mismatch only after it has client-rendered the root to recover,
|
|
254
|
+
* and that render resets `<html>`'s attributes to the ones it rendered —
|
|
255
|
+
* Chrome's `translated-ltr` class goes, `lang` returns to the served value —
|
|
256
|
+
* and replaces every `<font>` wrapper and hash-stamped element with fresh
|
|
257
|
+
* nodes. Measured 2026-10-12 by hydrating aglyn.com/ with a Chrome-style
|
|
258
|
+
* rewrite of one heading: the report fired with `<html lang="en">`, no class
|
|
259
|
+
* and no `<font>` left anywhere. Six es-US #418s arrived that day on
|
|
260
|
+
* aglyn.com/ and orbita-digital.aglyn.app/ (a Spanish site served as `en`);
|
|
261
|
+
* one carried `lang:en` — the beacon had been installed after the translator
|
|
262
|
+
* set `lang="es"`, so React's reset was the only rewrite left to see, and the
|
|
263
|
+
* same simulation reproduces exactly that label — and the other five carried
|
|
264
|
+
* nothing. The same two pages hydrated cleanly under es-US, en-GB and ru
|
|
265
|
+
* locales and time zones: nothing on them formats by the visitor's locale.
|
|
266
|
+
*
|
|
267
|
+
* So the mark is read when the translator LEAVES it: once at install, for a
|
|
268
|
+
* translator that ran first, and then from a `MutationObserver` until one is
|
|
269
|
+
* seen. A translator rewrites the page before React reaches it — that is what
|
|
270
|
+
* makes it a mismatch — so its mutations are delivered before the report.
|
|
271
|
+
*
|
|
272
|
+
* Cheap on purpose: one observer on `<html>`'s own `class`/`lang`, and one on
|
|
273
|
+
* the tree that looks only at added `<font>` elements and Microsoft's hash
|
|
274
|
+
* attributes, both disconnected as soon as a mark is found. Never throws, and
|
|
275
|
+
* a browser without `MutationObserver` gets the read-at-report behavior.
|
|
276
|
+
*/
|
|
277
|
+
export declare function watchPageTranslation(doc: Document, servedLang: string | null | undefined): () => string | undefined;
|
|
167
278
|
/**
|
|
168
279
|
* WHAT A REJECTED PROMISE ACTUALLY CARRIED (AGL-3279).
|
|
169
280
|
*
|
|
@@ -42,7 +42,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
42
42
|
* tenant runtime from its own, and neither needs React here. Deliberately NO
|
|
43
43
|
* 'use client' directive — inside this shared lib the directive forks the
|
|
44
44
|
* module graph (AGL-52); every importer is already a client module.
|
|
45
|
-
*/ import {
|
|
45
|
+
*/ import { browserFamily } from "./browser-family.js";
|
|
46
|
+
import { isCrossPoolDesync } from "./cross-pool-desync.js";
|
|
46
47
|
import { isForeignScriptUrl, stackFrameUrls } from "./foreign-script.js";
|
|
47
48
|
import { isStaleBuildError, recoverStaleBuildRejection } from "./stale-build-error.js";
|
|
48
49
|
const MAX_MESSAGE = 1024;
|
|
@@ -164,6 +165,60 @@ function clamp(value, max) {
|
|
|
164
165
|
export function isBenignBrowserNotice(message) {
|
|
165
166
|
return BENIGN_BROWSER_NOTICE.test(message);
|
|
166
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Whether the document is a page a visitor navigated to over the web
|
|
170
|
+
* (AGL-3759).
|
|
171
|
+
*
|
|
172
|
+
* Only `http:` and `https:` qualify. A document at `about:blank` or
|
|
173
|
+
* `about:srcdoc` is a COPY of our page: a translator extension or a site that
|
|
174
|
+
* frames our markup renders it into a fresh document with no URL of its own,
|
|
175
|
+
* and our scripts run there too. Next.js's app router then throws
|
|
176
|
+
* `Failed to construct 'URL': Invalid URL` resolving its canonical URL against
|
|
177
|
+
* `window.location.href`, which no code of ours can prevent and no visitor on
|
|
178
|
+
* the real page ever sees. `file:` (a saved page) and an extension's own
|
|
179
|
+
* scheme are copies in the same sense.
|
|
180
|
+
*
|
|
181
|
+
* A missing or unreadable location is not a web document either: nothing
|
|
182
|
+
* from it can be traced to a page we serve.
|
|
183
|
+
*/ export function isWebDocument(location) {
|
|
184
|
+
try {
|
|
185
|
+
const protocol = location == null ? void 0 : location.protocol;
|
|
186
|
+
return protocol === 'http:' || protocol === 'https:';
|
|
187
|
+
} catch (unused) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A rejection that says only "this was cancelled" (AGL-3734).
|
|
193
|
+
*
|
|
194
|
+
* `AbortError` is what a request rejects with when its `AbortSignal` fires —
|
|
195
|
+
* a deadline we set, a component that unmounted, a search the author typed
|
|
196
|
+
* past — and what the browser rejects every in-flight fetch with when the
|
|
197
|
+
* document goes away under it. On 2026-10-10 one arrived from
|
|
198
|
+
* `/edit-access`, the page a published site opens in a popup or a hidden
|
|
199
|
+
* iframe and then CLOSES itself the moment it has its answer. The report had
|
|
200
|
+
* no stack and no message past the name, so there was no call site to fix:
|
|
201
|
+
* the page's own mint was already caught, and whatever else the console shell
|
|
202
|
+
* had in flight was cut off by the site ending the page, which is the flow
|
|
203
|
+
* working.
|
|
204
|
+
*
|
|
205
|
+
* Why the classifier rather than each call site: every request on every
|
|
206
|
+
* console page can be the one in flight when a page is torn down, and the
|
|
207
|
+
* abort is the intended outcome in every case. There is nothing a fix of ours
|
|
208
|
+
* could change, and nothing in the report to locate it by.
|
|
209
|
+
*
|
|
210
|
+
* Narrow on purpose. Only `name === 'AbortError'`, and only for an UNHANDLED
|
|
211
|
+
* REJECTION — not `TimeoutError`, which is `AbortSignal.timeout` saying a
|
|
212
|
+
* deadline was MISSED (a real slowness), and not an uncaught `error` event or
|
|
213
|
+
* a `reportHandledError` call, where somebody chose to report it.
|
|
214
|
+
*/ export function isAbortRejection(reason) {
|
|
215
|
+
try {
|
|
216
|
+
return typeof reason === 'object' && reason !== null && reason.name === 'AbortError';
|
|
217
|
+
} catch (unused) {
|
|
218
|
+
// A getter that throws leaves the rejection reported, never dropped.
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
167
222
|
/**
|
|
168
223
|
* `kind` for Firebase's `auth/tenant-id-mismatch` — a sibling tab signed in to
|
|
169
224
|
* another account pool (AGL-3280). The console recovers the tab by reloading
|
|
@@ -275,6 +330,96 @@ export function isHydrationMismatch(message) {
|
|
|
275
330
|
}
|
|
276
331
|
return undefined;
|
|
277
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* A TRANSLATOR'S MARK, REMEMBERED FROM THE MOMENT IT APPEARS (AGL-3738).
|
|
335
|
+
*
|
|
336
|
+
* {@link pageTranslationSignal} read at the moment React reports a mismatch
|
|
337
|
+
* is reading a document the translator no longer owns. React reports a
|
|
338
|
+
* hydration mismatch only after it has client-rendered the root to recover,
|
|
339
|
+
* and that render resets `<html>`'s attributes to the ones it rendered —
|
|
340
|
+
* Chrome's `translated-ltr` class goes, `lang` returns to the served value —
|
|
341
|
+
* and replaces every `<font>` wrapper and hash-stamped element with fresh
|
|
342
|
+
* nodes. Measured 2026-10-12 by hydrating aglyn.com/ with a Chrome-style
|
|
343
|
+
* rewrite of one heading: the report fired with `<html lang="en">`, no class
|
|
344
|
+
* and no `<font>` left anywhere. Six es-US #418s arrived that day on
|
|
345
|
+
* aglyn.com/ and orbita-digital.aglyn.app/ (a Spanish site served as `en`);
|
|
346
|
+
* one carried `lang:en` — the beacon had been installed after the translator
|
|
347
|
+
* set `lang="es"`, so React's reset was the only rewrite left to see, and the
|
|
348
|
+
* same simulation reproduces exactly that label — and the other five carried
|
|
349
|
+
* nothing. The same two pages hydrated cleanly under es-US, en-GB and ru
|
|
350
|
+
* locales and time zones: nothing on them formats by the visitor's locale.
|
|
351
|
+
*
|
|
352
|
+
* So the mark is read when the translator LEAVES it: once at install, for a
|
|
353
|
+
* translator that ran first, and then from a `MutationObserver` until one is
|
|
354
|
+
* seen. A translator rewrites the page before React reaches it — that is what
|
|
355
|
+
* makes it a mismatch — so its mutations are delivered before the report.
|
|
356
|
+
*
|
|
357
|
+
* Cheap on purpose: one observer on `<html>`'s own `class`/`lang`, and one on
|
|
358
|
+
* the tree that looks only at added `<font>` elements and Microsoft's hash
|
|
359
|
+
* attributes, both disconnected as soon as a mark is found. Never throws, and
|
|
360
|
+
* a browser without `MutationObserver` gets the read-at-report behavior.
|
|
361
|
+
*/ export function watchPageTranslation(doc, servedLang) {
|
|
362
|
+
let seen = pageTranslationSignal(doc, servedLang);
|
|
363
|
+
const observers = [];
|
|
364
|
+
const stop = ()=>{
|
|
365
|
+
for (const observer of observers.splice(0))observer.disconnect();
|
|
366
|
+
};
|
|
367
|
+
const inspect = (records)=>{
|
|
368
|
+
if (seen) return;
|
|
369
|
+
let relevant = false;
|
|
370
|
+
for (const record of records){
|
|
371
|
+
if (record.type === 'attributes') {
|
|
372
|
+
relevant = true;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
for (const node of Array.from(record.addedNodes)){
|
|
376
|
+
if (node.nodeName === 'FONT') {
|
|
377
|
+
relevant = true;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (relevant) break;
|
|
382
|
+
}
|
|
383
|
+
if (!relevant) return;
|
|
384
|
+
seen = pageTranslationSignal(doc, servedLang);
|
|
385
|
+
if (seen) stop();
|
|
386
|
+
};
|
|
387
|
+
try {
|
|
388
|
+
const root = doc.documentElement;
|
|
389
|
+
if (!seen && root && typeof MutationObserver === 'function') {
|
|
390
|
+
const onRoot = new MutationObserver(inspect);
|
|
391
|
+
onRoot.observe(root, {
|
|
392
|
+
attributes: true,
|
|
393
|
+
attributeFilter: [
|
|
394
|
+
'class',
|
|
395
|
+
'lang'
|
|
396
|
+
]
|
|
397
|
+
});
|
|
398
|
+
const onTree = new MutationObserver(inspect);
|
|
399
|
+
onTree.observe(root, {
|
|
400
|
+
childList: true,
|
|
401
|
+
subtree: true,
|
|
402
|
+
attributes: true,
|
|
403
|
+
attributeFilter: [
|
|
404
|
+
'_msttexthash',
|
|
405
|
+
'_msthash'
|
|
406
|
+
]
|
|
407
|
+
});
|
|
408
|
+
observers.push(onRoot, onTree);
|
|
409
|
+
}
|
|
410
|
+
} catch (unused) {
|
|
411
|
+
// No observer: the report falls back to reading the document as it is.
|
|
412
|
+
}
|
|
413
|
+
return ()=>{
|
|
414
|
+
try {
|
|
415
|
+
// A rewrite in the same task as the report has not been delivered yet.
|
|
416
|
+
for (const observer of observers)inspect(observer.takeRecords());
|
|
417
|
+
return seen != null ? seen : pageTranslationSignal(doc, servedLang);
|
|
418
|
+
} catch (unused) {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
278
423
|
/**
|
|
279
424
|
* WHAT A REJECTED PROMISE ACTUALLY CARRIED (AGL-3279).
|
|
280
425
|
*
|
|
@@ -408,10 +553,24 @@ let installed = false;
|
|
|
408
553
|
// (the rejection handler below), because a tab left on a dead deploy is a
|
|
409
554
|
// visitor's problem whether or not its errors are counted.
|
|
410
555
|
const sampled = Math.random() < sampleRate;
|
|
556
|
+
// A copy of the page in a document that is not on the web reports nothing
|
|
557
|
+
// by either door, whatever it throws: see `isWebDocument`.
|
|
558
|
+
const reporting = sampled && isWebDocument(window.location);
|
|
411
559
|
// The document's language as SERVED, read before hydration and before any
|
|
412
560
|
// translator has had a chance to rewrite it (`pageTranslationSignal`).
|
|
413
561
|
const servedLang = (_ref3 = (_document_documentElement = document.documentElement) == null ? void 0 : _document_documentElement.getAttribute('lang')) != null ? _ref3 : null;
|
|
414
|
-
|
|
562
|
+
// Watched from now, because React's recovery erases the translator's marks
|
|
563
|
+
// before a mismatch is reported (`watchPageTranslation`, AGL-3738).
|
|
564
|
+
const translation = watchPageTranslation(document, servedLang);
|
|
565
|
+
if (reporting) raiseStackTraceLimit(BEACON_STACK_TRACE_LIMIT);
|
|
566
|
+
// Classified once, here, and only the family is kept: the user-agent
|
|
567
|
+
// string itself never reaches the queue (AGL-3732).
|
|
568
|
+
let browser = 'other';
|
|
569
|
+
try {
|
|
570
|
+
browser = browserFamily(navigator.userAgent);
|
|
571
|
+
} catch (unused) {
|
|
572
|
+
// An unreadable navigator is just `other`.
|
|
573
|
+
}
|
|
415
574
|
const seen = new Set();
|
|
416
575
|
let queued = [];
|
|
417
576
|
let sent = 0;
|
|
@@ -443,19 +602,21 @@ let installed = false;
|
|
|
443
602
|
};
|
|
444
603
|
const enqueue = (event)=>{
|
|
445
604
|
var _event_stack;
|
|
446
|
-
if (!
|
|
605
|
+
if (!reporting || sent >= maxPerPage) return;
|
|
447
606
|
// Message + first stack line: enough identity to collapse a render loop
|
|
448
607
|
// without collapsing distinct errors that share a message.
|
|
449
608
|
const key = `${event.message}\x00${((_event_stack = event.stack) != null ? _event_stack : '').split('\n', 2).join('\n')}`;
|
|
450
609
|
if (seen.has(key)) return;
|
|
451
610
|
seen.add(key);
|
|
452
611
|
sent += 1;
|
|
453
|
-
queued.push(event
|
|
612
|
+
queued.push(_extends({}, event, {
|
|
613
|
+
browser
|
|
614
|
+
}));
|
|
454
615
|
if (!timer) timer = setTimeout(flush, FLUSH_DELAY_MS);
|
|
455
616
|
};
|
|
456
|
-
// Null on
|
|
457
|
-
// rather than one by one and none by the other.
|
|
458
|
-
publishEvent =
|
|
617
|
+
// Null on a pageview that does not report, so it reports nothing by either
|
|
618
|
+
// door rather than one by one and none by the other.
|
|
619
|
+
publishEvent = reporting ? enqueue : null;
|
|
459
620
|
window.addEventListener('error', (event)=>{
|
|
460
621
|
try {
|
|
461
622
|
var _ref, _recoveredErrorKind;
|
|
@@ -481,7 +642,14 @@ let installed = false;
|
|
|
481
642
|
const kind = (_recoveredErrorKind = recoveredErrorKind(error != null ? error : {
|
|
482
643
|
message
|
|
483
644
|
})) != null ? _recoveredErrorKind : isHydrationMismatch(message) ? 'hydration' : 'error';
|
|
484
|
-
const translated = kind === 'hydration' ?
|
|
645
|
+
const translated = kind === 'hydration' ? translation() : undefined;
|
|
646
|
+
// A mismatch on a page a translator rewrote is the translator's, not
|
|
647
|
+
// ours (AGL-3738): React compared our render with text we did not
|
|
648
|
+
// write, recovered, and the visitor has a working page. Nothing we ship
|
|
649
|
+
// can change in response, so it goes the way of an injected stack. The
|
|
650
|
+
// mark is evidence on the document, not a guess about the visitor — an
|
|
651
|
+
// unmarked mismatch is still reported, and still the one to read.
|
|
652
|
+
if (translated) return;
|
|
485
653
|
enqueue(_extends({
|
|
486
654
|
kind,
|
|
487
655
|
message,
|
|
@@ -490,9 +658,7 @@ let installed = false;
|
|
|
490
658
|
line: typeof event.lineno === 'number' ? event.lineno : undefined,
|
|
491
659
|
col: typeof event.colno === 'number' ? event.colno : undefined,
|
|
492
660
|
url: pageUrl
|
|
493
|
-
},
|
|
494
|
-
translated
|
|
495
|
-
} : {}, kind === 'hydration' && navigator.language ? {
|
|
661
|
+
}, kind === 'hydration' && navigator.language ? {
|
|
496
662
|
language: clamp(navigator.language, 16)
|
|
497
663
|
} : {}));
|
|
498
664
|
} catch (unused) {
|
|
@@ -508,6 +674,9 @@ let installed = false;
|
|
|
508
674
|
// `recoverStaleBuildRejection`. A tab whose recovery is spent still
|
|
509
675
|
// reports it, as `chunk-load`.
|
|
510
676
|
if (recoverStaleBuildRejection(event.reason)) return;
|
|
677
|
+
// A cancelled request, ours or the browser's on teardown: see
|
|
678
|
+
// `isAbortRejection` for why this is dropped and how narrowly.
|
|
679
|
+
if (isAbortRejection(event.reason)) return;
|
|
511
680
|
const reason = event.reason;
|
|
512
681
|
const message = clamp(describeRejectionReason(event.reason), MAX_MESSAGE);
|
|
513
682
|
if (!message) return;
|