arcane-os 0.1.0-dev.5

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.
Files changed (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,1025 @@
1
+ import waitForComponent from './WaitForComponent.js';
2
+
3
+ const DEFAULT_DELAY_MS=2_000;
4
+ const DEFAULT_MAX_REPORTS_PER_SESSION=10;
5
+ const DEFAULT_MAX_REPORTS_PER_WINDOW=3;
6
+ const DEFAULT_MAX_PENDING_INCIDENTS=25;
7
+ const DEFAULT_RATE_WINDOW_MS=60_000;
8
+ const LEDGER_STORAGE_KEY='arcane-global-errors-v1';
9
+ const MAX_DETAIL_LENGTH=8_000;
10
+ const HANDLER_MARKER=Symbol.for('arcane.global-errors.handler');
11
+ const DEVELOPER_MODAL_HREF=new URL('../components/modal.html?v=13',import.meta.url).href;
12
+
13
+ const MESSAGE_STYLE=[
14
+ 'Write a simple plain-text email showing the error and, when the available details support it, a possible solution.',
15
+ 'If loop_detected or error_storm_detected is true, put that warning first and clearly state that further notifications have been suppressed.',
16
+ 'Do not add facts that are not present in the report data.',
17
+ ].join(' ');
18
+
19
+ function safeText(value,fallback=''){
20
+ if(value===undefined||value===null){
21
+ return fallback;
22
+ }
23
+
24
+ try{
25
+ const text=typeof value==='string' ? value:String(value);
26
+ return text.slice(0,MAX_DETAIL_LENGTH)||fallback;
27
+ }catch{
28
+ return fallback;
29
+ }
30
+ }
31
+
32
+ function safeNumber(value){
33
+ const number=Number(value);
34
+ return Number.isFinite(number) ? number:null;
35
+ }
36
+
37
+ function safeIso(timestamp){
38
+ try{
39
+ return new Date(timestamp).toISOString();
40
+ }catch{
41
+ return new Date(0).toISOString();
42
+ }
43
+ }
44
+
45
+ function resourceUrlFrom(event,target){
46
+ const resource=event?.target;
47
+ if(!resource||resource===target){
48
+ return '';
49
+ }
50
+
51
+ return safeText(resource.currentSrc||resource.src||resource.href);
52
+ }
53
+
54
+ /**
55
+ * Normalize a browser ErrorEvent or capture-phase resource error.
56
+ *
57
+ * @param {ErrorEvent|Event|Object} event
58
+ * @param {Window|Object} target
59
+ * @returns {Object}
60
+ */
61
+ export function normalizeErrorEvent(event={},target=globalThis.window){
62
+ const error=event?.error;
63
+ const resourceUrl=resourceUrlFrom(event,target);
64
+ const fallbackPath=safeText(target?.location?.pathname||target?.location?.href);
65
+
66
+ return {
67
+ type:'error',
68
+ message:safeText(
69
+ event?.message||error?.message,
70
+ resourceUrl ? 'Resource failed to load':'Unknown global error'
71
+ ),
72
+ name:safeText(
73
+ error?.name,
74
+ resourceUrl ? 'ResourceLoadError':'Error'
75
+ ),
76
+ stack:safeText(error?.stack)||null,
77
+ filename:safeText(event?.filename||resourceUrl||fallbackPath)||null,
78
+ lineno:safeNumber(event?.lineno),
79
+ colno:safeNumber(event?.colno),
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Normalize an unhandled Promise rejection, including non-Error reasons.
85
+ *
86
+ * @param {PromiseRejectionEvent|Object} event
87
+ * @param {Window|Object} target
88
+ * @returns {Object}
89
+ */
90
+ export function normalizeRejectionEvent(event={},target=globalThis.window){
91
+ const reason=event?.reason;
92
+ const reasonIsObject=reason!==null
93
+ && (typeof reason==='object'||typeof reason==='function');
94
+
95
+ return {
96
+ type:'unhandledrejection',
97
+ message:safeText(
98
+ reasonIsObject ? reason?.message:reason,
99
+ 'Unhandled promise rejection'
100
+ ),
101
+ name:safeText(
102
+ reasonIsObject ? reason?.name:'',
103
+ 'UnhandledRejection'
104
+ ),
105
+ stack:safeText(reasonIsObject ? reason?.stack:'')||null,
106
+ filename:safeText(target?.location?.pathname||target?.location?.href)||null,
107
+ lineno:null,
108
+ colno:null,
109
+ };
110
+ }
111
+
112
+ function normalizeFingerprintPart(value){
113
+ return safeText(value)
114
+ .trim()
115
+ .replace(/\s+/g,' ')
116
+ .slice(0,2_000);
117
+ }
118
+
119
+ function normalizeVolatileFingerprintPart(value){
120
+ return normalizeFingerprintPart(value)
121
+ .replace(/[?#][^\s)]+/g,'')
122
+ .replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi,'<uuid>')
123
+ .replace(/\b[0-9a-f]{16,}\b/gi,'<hex>')
124
+ .replace(/\b\d+(?:\.\d+)?\b/g,'<number>');
125
+ }
126
+
127
+ function normalizeStackFingerprint(value,name,message){
128
+ const stack=safeText(value).trim();
129
+ if(!stack){
130
+ return '';
131
+ }
132
+
133
+ const lines=stack.split(/\r?\n/);
134
+ const firstLine=lines[0].trim();
135
+ const errorName=safeText(name).trim();
136
+ const errorMessage=safeText(message).trim();
137
+ const hasMessageHeader=lines.length>1&&(
138
+ firstLine===errorMessage
139
+ || firstLine===`${errorName}: ${errorMessage}`
140
+ || (errorName&&firstLine.startsWith(`${errorName}:`))
141
+ || /^(?:Error|[A-Za-z_$][\w.$]*(?:Error|Exception)):(?:\s|$)/.test(firstLine)
142
+ );
143
+ const frames=hasMessageHeader ? lines.slice(1):lines;
144
+
145
+ return normalizeFingerprintPart(frames.join('\n'))
146
+ .replace(/[?#][^\s)]+/g,'')
147
+ .replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi,'<uuid>')
148
+ .replace(/\b[0-9a-f]{16,}\b/gi,'<hex>');
149
+ }
150
+
151
+ function hashText(value){
152
+ let first=0x811c9dc5;
153
+ let second=0x9e3779b9;
154
+
155
+ for(let index=0;index<value.length;index++){
156
+ const code=value.charCodeAt(index);
157
+ first=Math.imul(first^code,0x01000193);
158
+ second=Math.imul(second^code,0x85ebca6b);
159
+ }
160
+
161
+ return [first,second]
162
+ .map(hash => (hash>>>0).toString(16).padStart(8,'0'))
163
+ .join('');
164
+ }
165
+
166
+ /**
167
+ * Build a stable call-site signature. When a precise source location is
168
+ * available, the human-readable message is deliberately excluded so loops
169
+ * with changing counters, names, or timestamps still produce one report.
170
+ *
171
+ * @param {Object} incident
172
+ * @returns {string}
173
+ */
174
+ export function fingerprintIncident(incident){
175
+ const stackFingerprint=normalizeStackFingerprint(
176
+ incident?.stack,
177
+ incident?.name,
178
+ incident?.message
179
+ );
180
+ const hasPreciseSource=(
181
+ Number.isFinite(incident?.lineno)
182
+ || Number.isFinite(incident?.colno)
183
+ || Boolean(stackFingerprint)
184
+ );
185
+ const signature=[
186
+ normalizeFingerprintPart(incident?.type),
187
+ normalizeFingerprintPart(incident?.name),
188
+ hasPreciseSource
189
+ ? ''
190
+ : normalizeVolatileFingerprintPart(incident?.message),
191
+ normalizeFingerprintPart(incident?.filename).replace(/[?#].*$/,''),
192
+ normalizeFingerprintPart(incident?.lineno),
193
+ normalizeFingerprintPart(incident?.colno),
194
+ stackFingerprint,
195
+ ].join('|');
196
+
197
+ return `error-${hashText(signature)}`;
198
+ }
199
+
200
+ function defaultStorage(target){
201
+ try{
202
+ return target?.sessionStorage||null;
203
+ }catch{
204
+ return null;
205
+ }
206
+ }
207
+
208
+ async function sendWithWindowMail(target,...args){
209
+ if(typeof target?.mail?.send!=='function'){
210
+ await import('./Mail.js');
211
+ }
212
+
213
+ if(typeof target?.mail?.send!=='function'){
214
+ throw new Error('Mail notification service is unavailable');
215
+ }
216
+
217
+ return target.mail.send(...args);
218
+ }
219
+
220
+ function defaultDeveloperModeStatus(target){
221
+ try{
222
+ if(target?.user?.ready!==true){
223
+ return null;
224
+ }
225
+
226
+ return target.user.developer===true;
227
+ }catch{
228
+ return false;
229
+ }
230
+ }
231
+
232
+ function appendDeveloperDetail(document,list,label,value){
233
+ if(value===null||value===undefined||value===''){
234
+ return;
235
+ }
236
+
237
+ const term=document.createElement('dt');
238
+ const description=document.createElement('dd');
239
+
240
+ term.textContent=label;
241
+ term.style.fontWeight='bold';
242
+ description.textContent=safeText(value);
243
+ description.style.margin='0 0 0.75em';
244
+ description.style.overflowWrap='anywhere';
245
+ list.append(term,description);
246
+ }
247
+
248
+ function buildDeveloperIncidentContent(document,incident,fingerprint){
249
+ const content=document.createElement('section');
250
+ const heading=document.createElement('h2');
251
+ const introduction=document.createElement('p');
252
+ const details=document.createElement('dl');
253
+ const source=[
254
+ safeText(incident?.filename),
255
+ Number.isFinite(incident?.lineno) ? incident.lineno:'',
256
+ Number.isFinite(incident?.colno) ? incident.colno:'',
257
+ ].filter(value => value!=='').join(':');
258
+
259
+ content.className='developer-error-content';
260
+ heading.textContent='Application Error';
261
+ introduction.textContent='Developer mode captured this application error.';
262
+ details.style.margin='1.5em 0';
263
+
264
+ appendDeveloperDetail(document,details,'Type',incident?.type);
265
+ appendDeveloperDetail(document,details,'Name',incident?.name);
266
+ appendDeveloperDetail(document,details,'Message',incident?.message);
267
+ appendDeveloperDetail(document,details,'Source',source);
268
+ appendDeveloperDetail(document,details,'Fingerprint',fingerprint);
269
+
270
+ content.append(heading,introduction,details);
271
+
272
+ if(incident?.stack){
273
+ const stackHeading=document.createElement('h3');
274
+ const stack=document.createElement('pre');
275
+
276
+ stackHeading.textContent='Stack';
277
+ stack.textContent=safeText(incident.stack);
278
+ stack.style.overflowWrap='anywhere';
279
+ stack.style.whiteSpace='pre-wrap';
280
+ content.append(stackHeading,stack);
281
+ }
282
+
283
+ return content;
284
+ }
285
+
286
+ async function ensureHTMLImport(target){
287
+ const registry=target?.customElements||globalThis.customElements;
288
+
289
+ if(!registry?.get){
290
+ throw new Error('Custom elements are unavailable');
291
+ }
292
+
293
+ if(registry.get('html-import')){
294
+ return;
295
+ }
296
+
297
+ try{
298
+ await import('./HTMLImport.js');
299
+ }catch(error){
300
+ if(!registry.get('html-import')){
301
+ throw error;
302
+ }
303
+ }
304
+
305
+ if(!registry.get('html-import')){
306
+ throw new Error('The html-import component is unavailable');
307
+ }
308
+ }
309
+
310
+ async function presentDeveloperIncidentModal(target,incident,fingerprint){
311
+ const document=target?.document;
312
+ const container=document?.body||document?.documentElement;
313
+
314
+ if(!document?.createElement||!container?.append){
315
+ throw new Error('The document is not ready for an error modal');
316
+ }
317
+
318
+ await ensureHTMLImport(target);
319
+
320
+ const modal=document.createElement('html-import');
321
+ const content=buildDeveloperIncidentContent(document,incident,fingerprint);
322
+
323
+ modal.className='modal developer-error-modal';
324
+ modal.setAttribute('aria-label','Application error');
325
+ modal.setAttribute('data-global-error-modal',fingerprint);
326
+ modal.setAttribute('data-once','');
327
+ modal.setAttribute('href',DEVELOPER_MODAL_HREF);
328
+
329
+ let resolveClosed;
330
+ const closed=new Promise(resolve => {
331
+ resolveClosed=resolve;
332
+ });
333
+ const finish=() => {
334
+ target.removeEventListener?.('pagehide',onPageHide);
335
+ resolveClosed();
336
+ };
337
+ const onPageHide=() => {
338
+ try{
339
+ if(typeof modal.close==='function'){
340
+ modal.close(undefined,true);
341
+ }else{
342
+ modal.remove();
343
+ }
344
+ }catch{
345
+ modal.remove();
346
+ }
347
+ finish();
348
+ };
349
+
350
+ modal.addEventListener('modal-closed',finish,{ once:true });
351
+ target.addEventListener?.('pagehide',onPageHide,{ once:true });
352
+ container.append(modal);
353
+
354
+ try{
355
+ await waitForComponent(
356
+ modal,
357
+ {
358
+ event:'modal-ready',
359
+ methods:['populate','open'],
360
+ property:'ready',
361
+ }
362
+ );
363
+ await modal.populate(content,false);
364
+ await modal.open();
365
+ }catch(error){
366
+ target.removeEventListener?.('pagehide',onPageHide);
367
+ modal.removeEventListener('modal-closed',finish);
368
+ modal.remove();
369
+ resolveClosed();
370
+ throw error;
371
+ }
372
+
373
+ await closed;
374
+ }
375
+
376
+ class Errors {
377
+ constructor(options={}) {
378
+ const target=options.target||globalThis.window;
379
+ if(!target||typeof target.addEventListener!=='function'){
380
+ throw new TypeError('Errors requires an event target');
381
+ }
382
+
383
+ if(options.singleton!==false&&target.errors?.[HANDLER_MARKER]===true){
384
+ return target.errors;
385
+ }
386
+
387
+ this[HANDLER_MARKER]=true;
388
+ this.target=target;
389
+ this.delayMs=options.delayMs??DEFAULT_DELAY_MS;
390
+ this.deliveryTimeoutMs=options.deliveryTimeoutMs??45_000;
391
+ this.deliverySchedule=options.deliverySchedule
392
+ || globalThis.setTimeout.bind(globalThis);
393
+ this.deliveryCancel=options.deliveryCancel
394
+ || globalThis.clearTimeout.bind(globalThis);
395
+ this.maxPendingIncidents=options.maxPendingIncidents
396
+ ?? DEFAULT_MAX_PENDING_INCIDENTS;
397
+ this.maxReportsPerSession=options.maxReportsPerSession
398
+ ?? DEFAULT_MAX_REPORTS_PER_SESSION;
399
+ this.maxReportsPerWindow=options.maxReportsPerWindow
400
+ ?? DEFAULT_MAX_REPORTS_PER_WINDOW;
401
+ this.rateWindowMs=options.rateWindowMs??DEFAULT_RATE_WINDOW_MS;
402
+ this.now=options.now||Date.now;
403
+ this.schedule=options.schedule||globalThis.setTimeout.bind(globalThis);
404
+ this.cancel=options.cancel||globalThis.clearTimeout.bind(globalThis);
405
+ this.logger=options.logger||globalThis.console;
406
+ this.storage=options.storage===undefined
407
+ ? defaultStorage(target)
408
+ : options.storage;
409
+ this.storageHealthy=(
410
+ typeof this.storage?.getItem==='function'
411
+ && typeof this.storage?.setItem==='function'
412
+ );
413
+ this.sendMail=options.sendMail
414
+ || sendWithWindowMail.bind(null,target);
415
+ this.isDeveloperMode=typeof options.isDeveloperMode==='function'
416
+ ? options.isDeveloperMode
417
+ : defaultDeveloperModeStatus.bind(null,target);
418
+ this.presentDeveloperIncident=typeof options.presentDeveloperIncident==='function'
419
+ ? options.presentDeveloperIncident
420
+ : presentDeveloperIncidentModal.bind(null,target);
421
+
422
+ this.pending=new Map();
423
+ this.deliveryQueue=Promise.resolve();
424
+ this.destroyed=false;
425
+ this.invokingMail=false;
426
+ this.deferredDeveloperIncident=null;
427
+ this.developerModalActive=false;
428
+ this.developerUiDisabled=false;
429
+ this.developerShownFingerprints=new Set();
430
+ this.waitingForUser=false;
431
+
432
+ const ledger=this.loadLedger();
433
+ this.reported=new Set(ledger.fingerprints);
434
+ this.reportTimestamps=ledger.reportTimestamps;
435
+ this.circuitOpen=ledger.circuitOpen||!this.storageHealthy;
436
+
437
+ if(!this.storageHealthy){
438
+ this.warn(
439
+ 'Persistent suppression storage is unavailable; error email notifications are disabled to prevent reload loops.'
440
+ );
441
+ }
442
+
443
+ this.onError=this.onError.bind(this);
444
+ this.onRejection=this.onRejection.bind(this);
445
+ this.onUserLoaded=this.onUserLoaded.bind(this);
446
+
447
+ target.addEventListener('error',this.onError,true);
448
+ target.addEventListener('unhandledrejection',this.onRejection,true);
449
+
450
+ try{
451
+ target.document?.documentElement?.setAttribute(
452
+ 'data-global-error-handler',
453
+ 'active'
454
+ );
455
+ }catch{
456
+ // The marker is diagnostic only; listener installation already succeeded.
457
+ }
458
+
459
+ if(options.singleton!==false){
460
+ try{
461
+ target.errors=this;
462
+ }catch(error){
463
+ this.warn('Unable to expose the global error-handler singleton.',error);
464
+ }
465
+ }
466
+
467
+ this.restorePending(ledger.pending);
468
+ }
469
+
470
+ loadLedger(){
471
+ const empty={
472
+ fingerprints:[],
473
+ pending:[],
474
+ reportTimestamps:[],
475
+ circuitOpen:false,
476
+ };
477
+
478
+ if(!this.storageHealthy){
479
+ return empty;
480
+ }
481
+
482
+ try{
483
+ const value=JSON.parse(this.storage.getItem(LEDGER_STORAGE_KEY));
484
+ if(!value||typeof value!=='object'){
485
+ return empty;
486
+ }
487
+
488
+ return {
489
+ fingerprints:Array.isArray(value.fingerprints)
490
+ ? value.fingerprints.filter(item => typeof item==='string')
491
+ : [],
492
+ pending:Array.isArray(value.pending)
493
+ ? value.pending.filter(record => (
494
+ record
495
+ && typeof record==='object'
496
+ && typeof record.fingerprint==='string'
497
+ && record.incident
498
+ && typeof record.incident==='object'
499
+ ))
500
+ : [],
501
+ reportTimestamps:Array.isArray(value.reportTimestamps)
502
+ ? value.reportTimestamps.filter(Number.isFinite)
503
+ : [],
504
+ circuitOpen:value.circuitOpen===true,
505
+ };
506
+ }catch{
507
+ this.storageHealthy=false;
508
+ return empty;
509
+ }
510
+ }
511
+
512
+ persistLedger(){
513
+ if(!this.storageHealthy){
514
+ return false;
515
+ }
516
+
517
+ try{
518
+ const serialized=JSON.stringify({
519
+ fingerprints:[...this.reported],
520
+ pending:[...this.pending.values()].map(record => ({
521
+ count:record.count,
522
+ dueAt:record.dueAt,
523
+ errorStormDetected:record.errorStormDetected,
524
+ fingerprint:record.fingerprint,
525
+ firstSeen:record.firstSeen,
526
+ incident:record.incident,
527
+ lastSeen:record.lastSeen,
528
+ uniqueIncidentCount:record.uniqueIncidentCount,
529
+ })),
530
+ reportTimestamps:this.reportTimestamps,
531
+ circuitOpen:this.circuitOpen,
532
+ });
533
+ this.storage.setItem(LEDGER_STORAGE_KEY,serialized);
534
+ if(this.storage.getItem(LEDGER_STORAGE_KEY)!==serialized){
535
+ throw new Error('Suppression ledger verification failed');
536
+ }
537
+ return true;
538
+ }catch(error){
539
+ this.disableForStorageFailure(error);
540
+ return false;
541
+ }
542
+ }
543
+
544
+ disableForStorageFailure(error){
545
+ const shouldWarn=this.storageHealthy;
546
+ this.storageHealthy=false;
547
+ this.circuitOpen=true;
548
+
549
+ for(const record of this.pending.values()){
550
+ this.cancelTimer(record);
551
+ }
552
+ this.pending.clear();
553
+
554
+ if(shouldWarn){
555
+ this.warn(
556
+ 'Suppression storage failed; error email notifications are disabled to prevent reload loops.',
557
+ error
558
+ );
559
+ }
560
+ }
561
+
562
+ cancelTimer(record){
563
+ if(record?.timer===null||record?.timer===undefined){
564
+ return;
565
+ }
566
+
567
+ try{
568
+ this.cancel(record.timer);
569
+ }catch(error){
570
+ this.warn('Unable to cancel an error-notification timer.',error);
571
+ }finally{
572
+ record.timer=null;
573
+ }
574
+ }
575
+
576
+ scheduleRecord(record,delayMs){
577
+ try{
578
+ record.timer=this.schedule(
579
+ () => {
580
+ try{
581
+ this.flushFingerprint(record.fingerprint);
582
+ }catch(error){
583
+ this.warn('Unable to flush a scheduled error notification.',error);
584
+ }
585
+ },
586
+ Math.max(0,delayMs)
587
+ );
588
+ return true;
589
+ }catch(error){
590
+ this.pending.delete(record.fingerprint);
591
+ this.warn('Unable to schedule an error notification.',error);
592
+ return false;
593
+ }
594
+ }
595
+
596
+ restorePending(records){
597
+ if(this.circuitOpen||!Array.isArray(records)){
598
+ return;
599
+ }
600
+
601
+ const timestamp=this.now();
602
+ for(const storedRecord of records.slice(0,this.maxPendingIncidents)){
603
+ if(this.reported.has(storedRecord.fingerprint)){
604
+ continue;
605
+ }
606
+
607
+ const firstSeen=Number.isFinite(storedRecord.firstSeen)
608
+ ? storedRecord.firstSeen:timestamp;
609
+ const dueAt=Number.isFinite(storedRecord.dueAt)
610
+ ? storedRecord.dueAt:firstSeen+this.delayMs;
611
+ const record={
612
+ count:Number.isFinite(storedRecord.count)
613
+ ? Math.max(1,storedRecord.count):1,
614
+ dueAt,
615
+ errorStormDetected:storedRecord.errorStormDetected===true,
616
+ fingerprint:storedRecord.fingerprint,
617
+ firstSeen,
618
+ incident:storedRecord.incident,
619
+ lastSeen:Number.isFinite(storedRecord.lastSeen)
620
+ ? storedRecord.lastSeen:firstSeen,
621
+ timer:null,
622
+ uniqueIncidentCount:Number.isFinite(storedRecord.uniqueIncidentCount)
623
+ ? Math.max(1,storedRecord.uniqueIncidentCount):1,
624
+ };
625
+
626
+ this.pending.set(record.fingerprint,record);
627
+ this.scheduleRecord(record,dueAt-timestamp);
628
+ this.offerDeveloperIncident(record.incident,record.fingerprint);
629
+ }
630
+
631
+ this.persistLedger();
632
+ }
633
+
634
+ warn(message,error){
635
+ try{
636
+ this.logger?.warn?.(`[global-errors] ${message}`,error);
637
+ }catch{
638
+ // Error reporting must never create another error.
639
+ }
640
+ }
641
+
642
+ onError(event){
643
+ try{
644
+ this.capture(normalizeErrorEvent(event,this.target));
645
+ }catch(error){
646
+ this.warn('Unable to capture a global error.',error);
647
+ }
648
+ }
649
+
650
+ onRejection(event){
651
+ try{
652
+ this.capture(normalizeRejectionEvent(event,this.target));
653
+ }catch(error){
654
+ this.warn('Unable to capture an unhandled rejection.',error);
655
+ }
656
+ }
657
+
658
+ readDeveloperMode(){
659
+ try{
660
+ return this.isDeveloperMode();
661
+ }catch(error){
662
+ this.warn('Unable to read the developer-mode preference.',error);
663
+ return false;
664
+ }
665
+ }
666
+
667
+ deferDeveloperIncident(incident,fingerprint){
668
+ if(!this.deferredDeveloperIncident){
669
+ this.deferredDeveloperIncident={ fingerprint,incident };
670
+ }
671
+
672
+ if(this.waitingForUser){
673
+ return;
674
+ }
675
+
676
+ this.waitingForUser=true;
677
+ this.target.addEventListener(
678
+ 'user-entity-loaded',
679
+ this.onUserLoaded
680
+ );
681
+ }
682
+
683
+ onUserLoaded(){
684
+ this.target.removeEventListener(
685
+ 'user-entity-loaded',
686
+ this.onUserLoaded
687
+ );
688
+ this.waitingForUser=false;
689
+
690
+ const deferred=this.deferredDeveloperIncident;
691
+ this.deferredDeveloperIncident=null;
692
+
693
+ if(deferred){
694
+ this.offerDeveloperIncident(
695
+ deferred.incident,
696
+ deferred.fingerprint,
697
+ false
698
+ );
699
+ }
700
+ }
701
+
702
+ offerDeveloperIncident(incident,fingerprint,allowDefer=true){
703
+ if(
704
+ this.destroyed
705
+ || this.developerUiDisabled
706
+ || this.developerModalActive
707
+ || this.developerShownFingerprints.has(fingerprint)
708
+ ){
709
+ return false;
710
+ }
711
+
712
+ const developerMode=this.readDeveloperMode();
713
+ if(developerMode!==true){
714
+ if(allowDefer&&(developerMode===null||developerMode===undefined)){
715
+ this.deferDeveloperIncident(incident,fingerprint);
716
+ }
717
+ return false;
718
+ }
719
+
720
+ this.developerShownFingerprints.add(fingerprint);
721
+ this.developerModalActive=true;
722
+
723
+ let presentation;
724
+ try{
725
+ presentation=this.presentDeveloperIncident(incident,fingerprint);
726
+ }catch(error){
727
+ this.developerModalActive=false;
728
+ this.developerUiDisabled=true;
729
+ this.warn('Developer error display failed and has been disabled for this session.',error);
730
+ return false;
731
+ }
732
+
733
+ Promise.resolve(presentation).then(
734
+ () => {
735
+ this.developerModalActive=false;
736
+ },
737
+ error => {
738
+ this.developerModalActive=false;
739
+ this.developerUiDisabled=true;
740
+ this.warn('Developer error display failed and has been disabled for this session.',error);
741
+ }
742
+ );
743
+
744
+ return true;
745
+ }
746
+
747
+ capture(incident){
748
+ if(this.destroyed||this.invokingMail){
749
+ return false;
750
+ }
751
+
752
+ const fingerprint=fingerprintIncident(incident);
753
+ if(this.reported.has(fingerprint)){
754
+ return false;
755
+ }
756
+
757
+ this.offerDeveloperIncident(incident,fingerprint);
758
+
759
+ if(this.circuitOpen){
760
+ return false;
761
+ }
762
+
763
+ const timestamp=this.now();
764
+ const existing=this.pending.get(fingerprint);
765
+ if(existing){
766
+ existing.count++;
767
+ existing.lastSeen=timestamp;
768
+ if(existing.count===2){
769
+ return this.persistLedger();
770
+ }
771
+ return true;
772
+ }
773
+
774
+ if(this.pending.size>=this.maxPendingIncidents){
775
+ const stormRecord=this.pending.values().next().value;
776
+ if(stormRecord){
777
+ const stormAlreadyDetected=stormRecord.errorStormDetected;
778
+ stormRecord.errorStormDetected=true;
779
+ stormRecord.lastSeen=timestamp;
780
+ stormRecord.uniqueIncidentCount=stormAlreadyDetected
781
+ ? stormRecord.uniqueIncidentCount+1
782
+ : this.pending.size+1;
783
+ this.persistLedger();
784
+ }
785
+ return false;
786
+ }
787
+
788
+ const record={
789
+ count:1,
790
+ dueAt:timestamp+this.delayMs,
791
+ errorStormDetected:false,
792
+ fingerprint,
793
+ firstSeen:timestamp,
794
+ incident,
795
+ lastSeen:timestamp,
796
+ timer:null,
797
+ uniqueIncidentCount:1,
798
+ };
799
+
800
+ this.pending.set(fingerprint,record);
801
+ // This is intentionally a fixed window from the first occurrence.
802
+ // Resetting the timer on every repeat could postpone a true loop forever.
803
+ if(!this.scheduleRecord(record,this.delayMs)){
804
+ return false;
805
+ }
806
+
807
+ return this.persistLedger();
808
+ }
809
+
810
+ reserveDelivery(timestamp){
811
+ const cutoff=timestamp-this.rateWindowMs;
812
+ const recentReportTimestamps=this.reportTimestamps.filter(
813
+ reportTimestamp => reportTimestamp>=cutoff
814
+ );
815
+
816
+ if(
817
+ this.reportTimestamps.length>=this.maxReportsPerSession
818
+ || recentReportTimestamps.length>=this.maxReportsPerWindow
819
+ ){
820
+ this.openCircuit();
821
+ return { allowed:false,circuitOpened:true };
822
+ }
823
+
824
+ this.reportTimestamps.push(timestamp);
825
+
826
+ const circuitOpened=(
827
+ this.reportTimestamps.length>=this.maxReportsPerSession
828
+ || recentReportTimestamps.length+1>=this.maxReportsPerWindow
829
+ );
830
+
831
+ if(circuitOpened){
832
+ this.openCircuit();
833
+ }
834
+
835
+ return { allowed:true,circuitOpened };
836
+ }
837
+
838
+ openCircuit(){
839
+ this.circuitOpen=true;
840
+
841
+ for(const record of this.pending.values()){
842
+ this.cancelTimer(record);
843
+ }
844
+
845
+ this.pending.clear();
846
+ this.persistLedger();
847
+ }
848
+
849
+ flushFingerprint(fingerprint){
850
+ const record=this.pending.get(fingerprint);
851
+ if(!record){
852
+ return;
853
+ }
854
+
855
+ this.pending.delete(fingerprint);
856
+ this.cancelTimer(record);
857
+
858
+ if(this.destroyed||this.circuitOpen||this.reported.has(fingerprint)){
859
+ this.persistLedger();
860
+ return;
861
+ }
862
+
863
+ // Suppress before performing any fallible notification work. A failed
864
+ // mail request is an attempted report and must never be retried recursively.
865
+ this.reported.add(fingerprint);
866
+ const reservation=this.reserveDelivery(this.now());
867
+ if(record.errorStormDetected&&reservation.allowed&&!reservation.circuitOpened){
868
+ reservation.circuitOpened=true;
869
+ this.openCircuit();
870
+ }
871
+ const persisted=this.persistLedger();
872
+
873
+ if(!reservation.allowed||!persisted){
874
+ return;
875
+ }
876
+
877
+ this.deliveryQueue=this.deliveryQueue
878
+ .then(() => this.deliver(record,reservation))
879
+ .catch(error => {
880
+ this.warn('Unexpected notification-queue failure; no retry will be attempted.',error);
881
+ });
882
+ }
883
+
884
+ buildNotification(record,reservation){
885
+ const loopDetected=record.count>1;
886
+ const errorStormDetected=record.errorStormDetected
887
+ || reservation.circuitOpened;
888
+ const baseSubject=record.incident.type==='unhandledrejection'
889
+ ? 'ARCANE JS UNHANDLED REJECTION'
890
+ : 'ARCANE JS ERROR';
891
+
892
+ let subject=baseSubject;
893
+ if(errorStormDetected){
894
+ subject=`${baseSubject} - ERROR STORM DETECTED`;
895
+ }else if(loopDetected){
896
+ subject=`${baseSubject} - LOOP DETECTED`;
897
+ }
898
+
899
+ const payload={
900
+ ...record.incident,
901
+ fingerprint:record.fingerprint,
902
+ occurrence_count:record.count,
903
+ first_seen_at:safeIso(record.firstSeen),
904
+ last_seen_at:safeIso(record.lastSeen),
905
+ observation_window_ms:this.delayMs,
906
+ loop_detected:loopDetected,
907
+ error_storm_detected:errorStormDetected,
908
+ unique_incident_count:record.uniqueIncidentCount,
909
+ matching_notifications_suppressed:true,
910
+ loop_notice:loopDetected
911
+ ? `This error repeated ${record.count} times during the ${this.delayMs}ms observation window. Further matching notifications are suppressed for this browser session.`
912
+ : 'Further occurrences of this same error are suppressed for this browser session.',
913
+ circuit_breaker_notice:errorStormDetected
914
+ ? 'The global notification limit was reached. Further error emails are suppressed for this browser session to stop a possible varying error loop.'
915
+ : null,
916
+ };
917
+
918
+ return { payload,subject };
919
+ }
920
+
921
+ async deliver(record,reservation){
922
+ const { payload,subject }=this.buildNotification(record,reservation);
923
+ let timeout=null;
924
+ const delivery=Promise.resolve().then(() => {
925
+ // Only suppress events emitted synchronously by the adapter call.
926
+ // Its returned promise is observed below, so unrelated errors that
927
+ // occur while network delivery is pending must still be captured.
928
+ this.invokingMail=true;
929
+ try{
930
+ return this.sendMail([],subject,payload,MESSAGE_STYLE,'error');
931
+ }finally{
932
+ this.invokingMail=false;
933
+ }
934
+ });
935
+
936
+ try{
937
+ await Promise.race([
938
+ delivery,
939
+ new Promise((resolve,reject) => {
940
+ timeout=this.deliverySchedule(
941
+ () => reject(new Error('Error notification timed out')),
942
+ this.deliveryTimeoutMs
943
+ );
944
+ }),
945
+ ]);
946
+ }catch(error){
947
+ this.warn('Error notification failed; no retry will be attempted.',error);
948
+ }finally{
949
+ if(timeout!==null){
950
+ try{
951
+ this.deliveryCancel(timeout);
952
+ }catch(error){
953
+ this.warn('Unable to cancel the notification timeout.',error);
954
+ }
955
+ }
956
+ }
957
+ }
958
+
959
+ async flush(){
960
+ for(const fingerprint of [...this.pending.keys()]){
961
+ this.flushFingerprint(fingerprint);
962
+ }
963
+
964
+ await this.whenIdle();
965
+ }
966
+
967
+ async whenIdle(){
968
+ await this.deliveryQueue;
969
+ }
970
+
971
+ destroy(){
972
+ if(this.destroyed){
973
+ return;
974
+ }
975
+
976
+ this.destroyed=true;
977
+ this.target.removeEventListener('error',this.onError,true);
978
+ this.target.removeEventListener('unhandledrejection',this.onRejection,true);
979
+ this.target.removeEventListener('user-entity-loaded',this.onUserLoaded);
980
+ this.waitingForUser=false;
981
+ this.deferredDeveloperIncident=null;
982
+
983
+ try{
984
+ const modal=this.target.document?.querySelector?.(
985
+ 'html-import[data-global-error-modal]'
986
+ );
987
+ if(typeof modal?.close==='function'){
988
+ modal.close(undefined,true);
989
+ }else{
990
+ modal?.remove?.();
991
+ }
992
+ }catch(error){
993
+ this.warn('Unable to remove the developer error modal.',error);
994
+ }
995
+
996
+ try{
997
+ this.target.document?.documentElement?.removeAttribute(
998
+ 'data-global-error-handler'
999
+ );
1000
+ }catch{
1001
+ // The marker is diagnostic only.
1002
+ }
1003
+
1004
+ for(const record of this.pending.values()){
1005
+ this.cancelTimer(record);
1006
+ }
1007
+
1008
+ this.pending.clear();
1009
+ this.persistLedger();
1010
+
1011
+ if(this.target.errors===this){
1012
+ try{
1013
+ delete this.target.errors;
1014
+ }catch{
1015
+ this.target.errors=undefined;
1016
+ }
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ if(typeof window!=='undefined'&&window.errors?.[HANDLER_MARKER]!==true){
1022
+ new Errors();
1023
+ }
1024
+
1025
+ export default Errors;