arcane-os 0.2.3 → 0.3.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.
- package/CHANGELOG.md +17 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +20 -20
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +2 -2
- package/docs/reference/behavioral-testing.md +28 -5
- package/docs/reference/cli.md +115 -7
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +86 -37
- package/docs/reference/inventory/runtime-modules.json +126 -49
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +143 -33
- package/docs/reference/runtime-components.md +192 -80
- package/docs/reference/runtime-modules.md +584 -70
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +453 -131
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +244 -72
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +235 -37
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -2,8 +2,22 @@ import {
|
|
|
2
2
|
APP_LOCAL_STORAGE_PREFIX,
|
|
3
3
|
resolveBrowserApplicationId
|
|
4
4
|
} from './AppDataScope.js';
|
|
5
|
+
import {
|
|
6
|
+
createArcaneEventSource,
|
|
7
|
+
projectArcaneDOMEvent
|
|
8
|
+
} from 'arcane-os/event-manager';
|
|
9
|
+
|
|
10
|
+
const PUBLISH_DBLS_READY=Symbol('publish-dbls-ready');
|
|
11
|
+
export const DBLS_EVENT_TYPES=Object.freeze({
|
|
12
|
+
ready:'dbls-ready'
|
|
13
|
+
});
|
|
14
|
+
export const DBLS_REASONS=Object.freeze({
|
|
15
|
+
ready:'local-storage-adapter-ready'
|
|
16
|
+
});
|
|
5
17
|
|
|
6
18
|
class DBLS {
|
|
19
|
+
#events;
|
|
20
|
+
|
|
7
21
|
constructor({
|
|
8
22
|
applicationId=null,
|
|
9
23
|
documentObject=globalThis.document,
|
|
@@ -27,6 +41,10 @@ class DBLS {
|
|
|
27
41
|
});
|
|
28
42
|
this.storage=storage;
|
|
29
43
|
this.storagePrefix=`${APP_LOCAL_STORAGE_PREFIX}${this.applicationId}:`;
|
|
44
|
+
this.#events=createArcaneEventSource(this,{
|
|
45
|
+
source:'dbls',
|
|
46
|
+
eventTypes:Object.freeze(Object.values(DBLS_EVENT_TYPES))
|
|
47
|
+
});
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
ready=false;
|
|
@@ -153,19 +171,34 @@ class DBLS {
|
|
|
153
171
|
count() {
|
|
154
172
|
return this.logicalKeys().length;
|
|
155
173
|
}
|
|
174
|
+
|
|
175
|
+
[PUBLISH_DBLS_READY](){
|
|
176
|
+
const detail=Object.freeze({
|
|
177
|
+
dbls:this,
|
|
178
|
+
applicationId:this.applicationId,
|
|
179
|
+
reason:DBLS_REASONS.ready
|
|
180
|
+
});
|
|
181
|
+
const {occurrence}=this.#events.dispatch(
|
|
182
|
+
DBLS_EVENT_TYPES.ready,
|
|
183
|
+
detail,
|
|
184
|
+
{
|
|
185
|
+
operationId:`dbls-ready-${this.#events.instanceId}`,
|
|
186
|
+
publicDetail:Object.freeze({
|
|
187
|
+
applicationId:this.applicationId,
|
|
188
|
+
ready:true,
|
|
189
|
+
reason:DBLS_REASONS.ready
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
projectArcaneDOMEvent(window,occurrence);
|
|
194
|
+
}
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
if(typeof window.dbls?.get !== "function"){
|
|
159
198
|
window.dbls=new DBLS();
|
|
160
199
|
window.dbls.ready=true;
|
|
161
200
|
|
|
162
|
-
|
|
163
|
-
'dbls-ready', {
|
|
164
|
-
detail: { dbls: window.dbls }
|
|
165
|
-
}
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
window.dispatchEvent(dblsReady);
|
|
201
|
+
window.dbls[PUBLISH_DBLS_READY]();
|
|
169
202
|
}
|
|
170
203
|
|
|
171
204
|
export default DBLS;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import Is from "../../node_modules/strong-type/index.js";
|
|
2
2
|
import {openApplicationDataDirectory} from './AppDataScope.js';
|
|
3
|
+
import {
|
|
4
|
+
createArcaneEventSource,
|
|
5
|
+
projectArcaneDOMEvent
|
|
6
|
+
} from 'arcane-os/event-manager';
|
|
3
7
|
const is=new Is(false);
|
|
4
8
|
|
|
9
|
+
export const DBOPFS_EVENT_TYPES=Object.freeze({
|
|
10
|
+
ready:'dbopfs-ready'
|
|
11
|
+
});
|
|
12
|
+
export const DBOPFS_REASONS=Object.freeze({
|
|
13
|
+
ready:'opfs-database-ready'
|
|
14
|
+
});
|
|
15
|
+
|
|
5
16
|
const DEFAULT_TABLE_DIRECTORIES=Object.freeze({
|
|
6
17
|
users:'users',
|
|
7
18
|
scores:'scores',
|
|
@@ -94,6 +105,8 @@ if(navigator.storage?.persist){
|
|
|
94
105
|
*/
|
|
95
106
|
class DBOPFS {
|
|
96
107
|
|
|
108
|
+
#events;
|
|
109
|
+
|
|
97
110
|
/** @type {FileSystemDirectoryHandle|Object} */
|
|
98
111
|
#db={}
|
|
99
112
|
|
|
@@ -270,6 +283,10 @@ class DBOPFS {
|
|
|
270
283
|
return window.dbopfs;
|
|
271
284
|
}
|
|
272
285
|
|
|
286
|
+
this.#events=createArcaneEventSource(this,{
|
|
287
|
+
source:'dbopfs',
|
|
288
|
+
eventTypes:Object.freeze(Object.values(DBOPFS_EVENT_TYPES))
|
|
289
|
+
});
|
|
273
290
|
this.readyPromise=this.#init(options);
|
|
274
291
|
}
|
|
275
292
|
|
|
@@ -294,18 +311,25 @@ class DBOPFS {
|
|
|
294
311
|
|
|
295
312
|
this.ready=true;
|
|
296
313
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
314
|
+
const {occurrence}=this.#events.dispatch(
|
|
315
|
+
DBOPFS_EVENT_TYPES.ready,
|
|
316
|
+
Object.freeze({
|
|
317
|
+
dbopfs:this,
|
|
318
|
+
applicationId:this.#applicationId,
|
|
319
|
+
storagePath:this.#storagePath,
|
|
320
|
+
reason:DBOPFS_REASONS.ready
|
|
321
|
+
}),
|
|
322
|
+
{
|
|
323
|
+
operationId:`dbopfs-ready-${this.#events.instanceId}`,
|
|
324
|
+
publicDetail:Object.freeze({
|
|
325
|
+
applicationId:this.#applicationId,
|
|
326
|
+
ready:true,
|
|
327
|
+
reason:DBOPFS_REASONS.ready,
|
|
328
|
+
storagePath:this.#storagePath
|
|
329
|
+
})
|
|
330
|
+
}
|
|
308
331
|
);
|
|
332
|
+
projectArcaneDOMEvent(window,occurrence);
|
|
309
333
|
}
|
|
310
334
|
|
|
311
335
|
async #createDefaultTables(){
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './DBOPFS.js';
|
|
2
2
|
import {hasUserEntry} from './ChatRecords.js';
|
|
3
3
|
import {hasMemoryContent} from './MemoryRecords.js';
|
|
4
|
+
import {arcaneEvents} from 'arcane-os/event-manager';
|
|
4
5
|
|
|
5
6
|
async function clearEmptyChatsAndMemories(){
|
|
6
7
|
await waitForDBOPFS();
|
|
@@ -70,12 +71,21 @@ function waitForDBOPFS(){
|
|
|
70
71
|
|
|
71
72
|
return new Promise(
|
|
72
73
|
function waitForDBOPFSPromise(resolve){
|
|
74
|
+
let settled=false;
|
|
75
|
+
let unsubscribe;
|
|
73
76
|
function ready(){
|
|
74
|
-
window.
|
|
77
|
+
if(settled||window.dbopfs?.ready!==true){
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
settled=true;
|
|
81
|
+
unsubscribe?.();
|
|
75
82
|
resolve(window.dbopfs);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
unsubscribe=arcaneEvents.subscribe(
|
|
86
|
+
'dbopfs-ready',
|
|
87
|
+
ready
|
|
88
|
+
);
|
|
79
89
|
|
|
80
90
|
if(window.dbopfs?.ready){
|
|
81
91
|
ready();
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import waitForComponent from './WaitForComponent.js';
|
|
2
|
+
import {
|
|
3
|
+
arcaneEvents,
|
|
4
|
+
createArcaneEventSource
|
|
5
|
+
} from 'arcane-os/event-manager';
|
|
2
6
|
|
|
3
7
|
const DEFAULT_DELAY_MS=2_000;
|
|
4
8
|
const DEFAULT_MAX_REPORTS_PER_SESSION=10;
|
|
@@ -10,6 +14,19 @@ const MAX_DETAIL_LENGTH=8_000;
|
|
|
10
14
|
const HANDLER_MARKER=Symbol.for('arcane.global-errors.handler');
|
|
11
15
|
const DEVELOPER_MODAL_HREF=new URL('../components/modal.html?v=13',import.meta.url).href;
|
|
12
16
|
|
|
17
|
+
export const GLOBAL_ERROR_EVENT_TYPES=Object.freeze({
|
|
18
|
+
browserErrorCaptured:'arcane-error-captured',
|
|
19
|
+
unhandledRejectionCaptured:'arcane-unhandled-rejection-captured'
|
|
20
|
+
});
|
|
21
|
+
export const GLOBAL_ERROR_EVENT_CODES=Object.freeze({
|
|
22
|
+
browserErrorCaptured:'ARCANE_BROWSER_ERROR_CAPTURED',
|
|
23
|
+
unhandledRejectionCaptured:'ARCANE_UNHANDLED_REJECTION_CAPTURED'
|
|
24
|
+
});
|
|
25
|
+
export const GLOBAL_ERROR_REASONS=Object.freeze({
|
|
26
|
+
browserErrorCaptured:'browser-error-captured',
|
|
27
|
+
unhandledRejectionCaptured:'unhandled-promise-rejection-captured'
|
|
28
|
+
});
|
|
29
|
+
|
|
13
30
|
const MESSAGE_STYLE=[
|
|
14
31
|
'Write a simple plain-text email showing the error and, when the available details support it, a possible solution.',
|
|
15
32
|
'If loop_detected or error_storm_detected is true, put that warning first and clearly state that further notifications have been suppressed.',
|
|
@@ -374,6 +391,12 @@ async function presentDeveloperIncidentModal(target,incident,fingerprint){
|
|
|
374
391
|
}
|
|
375
392
|
|
|
376
393
|
class Errors {
|
|
394
|
+
#events;
|
|
395
|
+
|
|
396
|
+
#operationSequence=0;
|
|
397
|
+
|
|
398
|
+
#stopUserLoaded=null;
|
|
399
|
+
|
|
377
400
|
constructor(options={}) {
|
|
378
401
|
const target=options.target||globalThis.window;
|
|
379
402
|
if(!target||typeof target.addEventListener!=='function'){
|
|
@@ -384,6 +407,13 @@ class Errors {
|
|
|
384
407
|
return target.errors;
|
|
385
408
|
}
|
|
386
409
|
|
|
410
|
+
this.#events=createArcaneEventSource(this,{
|
|
411
|
+
source:'global-error-handler',
|
|
412
|
+
eventTypes:Object.freeze([
|
|
413
|
+
GLOBAL_ERROR_EVENT_TYPES.browserErrorCaptured,
|
|
414
|
+
GLOBAL_ERROR_EVENT_TYPES.unhandledRejectionCaptured
|
|
415
|
+
])
|
|
416
|
+
});
|
|
387
417
|
this[HANDLER_MARKER]=true;
|
|
388
418
|
this.target=target;
|
|
389
419
|
this.delayMs=options.delayMs??DEFAULT_DELAY_MS;
|
|
@@ -674,17 +704,15 @@ class Errors {
|
|
|
674
704
|
}
|
|
675
705
|
|
|
676
706
|
this.waitingForUser=true;
|
|
677
|
-
this.
|
|
707
|
+
this.#stopUserLoaded=arcaneEvents.subscribe(
|
|
678
708
|
'user-entity-loaded',
|
|
679
|
-
this.onUserLoaded
|
|
709
|
+
this.onUserLoaded,
|
|
710
|
+
{once:true}
|
|
680
711
|
);
|
|
681
712
|
}
|
|
682
713
|
|
|
683
714
|
onUserLoaded(){
|
|
684
|
-
this
|
|
685
|
-
'user-entity-loaded',
|
|
686
|
-
this.onUserLoaded
|
|
687
|
-
);
|
|
715
|
+
this.#stopUserLoaded=null;
|
|
688
716
|
this.waitingForUser=false;
|
|
689
717
|
|
|
690
718
|
const deferred=this.deferredDeveloperIncident;
|
|
@@ -754,6 +782,33 @@ class Errors {
|
|
|
754
782
|
return false;
|
|
755
783
|
}
|
|
756
784
|
|
|
785
|
+
const incidentKind=incident?.type==='unhandledrejection'
|
|
786
|
+
?'unhandled-promise-rejection'
|
|
787
|
+
:'browser-error';
|
|
788
|
+
const eventType=incidentKind==='unhandled-promise-rejection'
|
|
789
|
+
?GLOBAL_ERROR_EVENT_TYPES.unhandledRejectionCaptured
|
|
790
|
+
:GLOBAL_ERROR_EVENT_TYPES.browserErrorCaptured;
|
|
791
|
+
const code=incidentKind==='unhandled-promise-rejection'
|
|
792
|
+
?GLOBAL_ERROR_EVENT_CODES.unhandledRejectionCaptured
|
|
793
|
+
:GLOBAL_ERROR_EVENT_CODES.browserErrorCaptured;
|
|
794
|
+
const reason=incidentKind==='unhandled-promise-rejection'
|
|
795
|
+
?GLOBAL_ERROR_REASONS.unhandledRejectionCaptured
|
|
796
|
+
:GLOBAL_ERROR_REASONS.browserErrorCaptured;
|
|
797
|
+
this.#operationSequence+=1;
|
|
798
|
+
this.#events.dispatch(
|
|
799
|
+
eventType,
|
|
800
|
+
Object.freeze({...incident,code,fingerprint,reason}),
|
|
801
|
+
{
|
|
802
|
+
operationId:`global-error-handler-${this.#events.instanceId}-${this.#operationSequence.toString(36)}`,
|
|
803
|
+
publicDetail:Object.freeze({
|
|
804
|
+
id:fingerprint,
|
|
805
|
+
code,
|
|
806
|
+
kind:incidentKind,
|
|
807
|
+
reason
|
|
808
|
+
})
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
|
|
757
812
|
this.offerDeveloperIncident(incident,fingerprint);
|
|
758
813
|
|
|
759
814
|
if(this.circuitOpen){
|
|
@@ -976,7 +1031,8 @@ class Errors {
|
|
|
976
1031
|
this.destroyed=true;
|
|
977
1032
|
this.target.removeEventListener('error',this.onError,true);
|
|
978
1033
|
this.target.removeEventListener('unhandledrejection',this.onRejection,true);
|
|
979
|
-
this
|
|
1034
|
+
this.#stopUserLoaded?.();
|
|
1035
|
+
this.#stopUserLoaded=null;
|
|
980
1036
|
this.waitingForUser=false;
|
|
981
1037
|
this.deferredDeveloperIncident=null;
|
|
982
1038
|
|
|
@@ -1015,6 +1071,8 @@ class Errors {
|
|
|
1015
1071
|
this.target.errors=undefined;
|
|
1016
1072
|
}
|
|
1017
1073
|
}
|
|
1074
|
+
|
|
1075
|
+
this.#events.dispose();
|
|
1018
1076
|
}
|
|
1019
1077
|
}
|
|
1020
1078
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createArcaneEventSource,
|
|
3
|
+
projectArcaneDOMEvent
|
|
4
|
+
} from 'arcane-os/event-manager';
|
|
5
|
+
|
|
1
6
|
const htmlImportHostRegistryKey=Symbol.for('arcane.html-import.hosts');
|
|
2
7
|
const htmlImportHostRegistry=globalThis[htmlImportHostRegistryKey] instanceof Map
|
|
3
8
|
?globalThis[htmlImportHostRegistryKey]
|
|
@@ -7,7 +12,24 @@ globalThis[htmlImportHostRegistryKey]=htmlImportHostRegistry;
|
|
|
7
12
|
|
|
8
13
|
let htmlImportScriptId=0;
|
|
9
14
|
|
|
15
|
+
function samePropertyDescriptor(left,right){
|
|
16
|
+
if(!left||!right)return left===right;
|
|
17
|
+
return left.configurable===right.configurable
|
|
18
|
+
&&left.enumerable===right.enumerable
|
|
19
|
+
&&left.writable===right.writable
|
|
20
|
+
&&left.value===right.value
|
|
21
|
+
&&left.get===right.get
|
|
22
|
+
&&left.set===right.set;
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
class HTMLImport extends HTMLElement {
|
|
26
|
+
#connectionGeneration=0;
|
|
27
|
+
#eventOwner=Object.freeze({kind:'html-import-lifecycle-owner'});
|
|
28
|
+
#events;
|
|
29
|
+
#importedDestroy=null;
|
|
30
|
+
#loadController=null;
|
|
31
|
+
#loadTask=Promise.resolve();
|
|
32
|
+
|
|
11
33
|
ready=false;
|
|
12
34
|
|
|
13
35
|
constructor() {
|
|
@@ -15,12 +37,40 @@ class HTMLImport extends HTMLElement {
|
|
|
15
37
|
this.attachShadow({ mode: 'open' });
|
|
16
38
|
}
|
|
17
39
|
|
|
18
|
-
|
|
40
|
+
#createEvents(){
|
|
41
|
+
this.#events=createArcaneEventSource(this.#eventOwner,{
|
|
42
|
+
source:'html-import',
|
|
43
|
+
eventTypes:Object.freeze([
|
|
44
|
+
'html-import-error',
|
|
45
|
+
'html-import-ready'
|
|
46
|
+
])
|
|
47
|
+
});
|
|
48
|
+
return this.#events;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
connectedCallback() {
|
|
52
|
+
const generation=++this.#connectionGeneration;
|
|
53
|
+
const controller=new AbortController();
|
|
19
54
|
this.ready=false;
|
|
55
|
+
this.#loadController?.abort('html-import-load-superseded');
|
|
56
|
+
this.#loadController=controller;
|
|
57
|
+
const previous=this.#loadTask;
|
|
58
|
+
const task=Promise.resolve(previous)
|
|
59
|
+
.catch(()=>{})
|
|
60
|
+
.then(()=>this.#connect(generation,controller));
|
|
61
|
+
this.#loadTask=task;
|
|
62
|
+
return task;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async #connect(generation,controller){
|
|
66
|
+
if(!this.#isCurrentConnection(generation,controller))return;
|
|
67
|
+
if(!this.#events||this.#events.disposed)this.#createEvents();
|
|
20
68
|
const href=this.getAttribute('href');
|
|
21
69
|
let resolvedHref='';
|
|
70
|
+
const operationId=`html-import-load-${this.#events.instanceId}-${generation}`;
|
|
22
71
|
if(!href){
|
|
23
72
|
console.log('no href provided for html-import tag',this);
|
|
73
|
+
this.#loadController=null;
|
|
24
74
|
return;
|
|
25
75
|
}
|
|
26
76
|
|
|
@@ -35,46 +85,167 @@ class HTMLImport extends HTMLElement {
|
|
|
35
85
|
cache:'default',
|
|
36
86
|
credentials:'same-origin',
|
|
37
87
|
method:'GET',
|
|
38
|
-
redirect:'error'
|
|
88
|
+
redirect:'error',
|
|
89
|
+
signal:controller.signal
|
|
39
90
|
});
|
|
40
|
-
|
|
91
|
+
if(!this.#isCurrentConnection(generation,controller))return;
|
|
92
|
+
await this.#loadHTML(href,resolvedHref,response,generation,controller,operationId);
|
|
41
93
|
}catch(err){
|
|
94
|
+
if(!this.#isCurrentConnection(generation,controller)){
|
|
95
|
+
await this.#destroyImportedHost();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
42
98
|
console.error('Error loading HTML component:',err);
|
|
43
|
-
|
|
44
|
-
bubbles:true,
|
|
45
|
-
composed:true,
|
|
46
|
-
detail:{
|
|
99
|
+
const detail=Object.freeze({
|
|
47
100
|
code:'HTML_IMPORT_FAILED',
|
|
48
101
|
href,
|
|
49
102
|
message:'The component could not be loaded.',
|
|
103
|
+
reason:'component-import-rejected',
|
|
50
104
|
resolvedHref
|
|
105
|
+
});
|
|
106
|
+
const {occurrence}=this.#events.dispatch(
|
|
107
|
+
'html-import-error',
|
|
108
|
+
detail,
|
|
109
|
+
{
|
|
110
|
+
operationId,
|
|
111
|
+
publicDetail:Object.freeze({code:detail.code,reason:detail.reason})
|
|
51
112
|
}
|
|
52
|
-
|
|
113
|
+
);
|
|
114
|
+
projectArcaneDOMEvent(this,occurrence,{
|
|
115
|
+
bubbles:true,
|
|
116
|
+
composed:true
|
|
117
|
+
});
|
|
118
|
+
await this.#destroyImportedHost();
|
|
119
|
+
}finally{
|
|
120
|
+
if(this.#loadController===controller)this.#loadController=null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
disconnectedCallback(){
|
|
125
|
+
this.ready=false;
|
|
126
|
+
++this.#connectionGeneration;
|
|
127
|
+
this.#loadController?.abort('html-import-disconnected');
|
|
128
|
+
this.#loadController=null;
|
|
129
|
+
const events=this.#events;
|
|
130
|
+
const previous=this.#loadTask;
|
|
131
|
+
const immediateTeardown=this.#destroyImportedHost();
|
|
132
|
+
const task=Promise.allSettled([previous,immediateTeardown]).then(()=>{
|
|
133
|
+
events?.dispose();
|
|
134
|
+
if(this.#events===events)this.#events=null;
|
|
135
|
+
});
|
|
136
|
+
this.#loadTask=task;
|
|
137
|
+
return task;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#isCurrentConnection(generation,controller){
|
|
141
|
+
return this.isConnected
|
|
142
|
+
&&generation===this.#connectionGeneration
|
|
143
|
+
&&this.#loadController===controller
|
|
144
|
+
&&!controller.signal.aborted;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#reportImportedDestroyError(error,{events,href,operationId}){
|
|
148
|
+
console.error('Error destroying imported HTML component:',error);
|
|
149
|
+
if(!events||events.disposed)return;
|
|
150
|
+
const detail=Object.freeze({
|
|
151
|
+
code:'HTML_IMPORT_IMPORTED_COMPONENT_DESTROY_REJECTED',
|
|
152
|
+
href,
|
|
153
|
+
message:'The imported component could not be destroyed.',
|
|
154
|
+
reason:'imported-component-destroy-rejected'
|
|
155
|
+
});
|
|
156
|
+
const {occurrence}=events.dispatch(
|
|
157
|
+
'html-import-error',
|
|
158
|
+
detail,
|
|
159
|
+
{
|
|
160
|
+
operationId,
|
|
161
|
+
publicDetail:Object.freeze({code:detail.code,reason:detail.reason})
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
projectArcaneDOMEvent(this,occurrence,{
|
|
165
|
+
bubbles:true,
|
|
166
|
+
composed:true
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#restoreImportedDestroy(record){
|
|
171
|
+
const current=Object.getOwnPropertyDescriptor(this,'destroy')??null;
|
|
172
|
+
if(!samePropertyDescriptor(current,record.installedDescriptor))return false;
|
|
173
|
+
if(record.previousDescriptor){
|
|
174
|
+
Object.defineProperty(this,'destroy',record.previousDescriptor);
|
|
175
|
+
}else{
|
|
176
|
+
delete this.destroy;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#destroyImportedHost(){
|
|
182
|
+
const record=this.#importedDestroy;
|
|
183
|
+
if(!record)return Promise.resolve(false);
|
|
184
|
+
this.#importedDestroy=null;
|
|
185
|
+
const events=this.#events;
|
|
186
|
+
const generation=this.#connectionGeneration;
|
|
187
|
+
const reportContext={
|
|
188
|
+
events,
|
|
189
|
+
href:this.getAttribute('href')||'',
|
|
190
|
+
operationId:events
|
|
191
|
+
?`html-import-destroy-${events.instanceId}-${generation}`
|
|
192
|
+
:null
|
|
193
|
+
};
|
|
194
|
+
let result;
|
|
195
|
+
try{
|
|
196
|
+
result=record.destroy.call(this);
|
|
197
|
+
}catch(error){
|
|
198
|
+
this.#reportImportedDestroyError(error,reportContext);
|
|
199
|
+
this.#restoreImportedDestroy(record);
|
|
200
|
+
return Promise.resolve(false);
|
|
53
201
|
}
|
|
202
|
+
return Promise.resolve(result)
|
|
203
|
+
.then(
|
|
204
|
+
()=>true,
|
|
205
|
+
error=>{
|
|
206
|
+
this.#reportImportedDestroyError(error,reportContext);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
.finally(()=>this.#restoreImportedDestroy(record));
|
|
54
211
|
}
|
|
55
212
|
|
|
56
|
-
async #loadHTML(href,resolvedHref,response){
|
|
213
|
+
async #loadHTML(href,resolvedHref,response,generation,controller,operationId){
|
|
57
214
|
if (!response.ok) {
|
|
58
215
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
59
216
|
}
|
|
60
217
|
|
|
61
218
|
const html = await response.text();
|
|
219
|
+
if(!this.#isCurrentConnection(generation,controller))return false;
|
|
220
|
+
await this.#destroyImportedHost();
|
|
221
|
+
if(!this.#isCurrentConnection(generation,controller))return false;
|
|
62
222
|
this.shadowRoot.innerHTML = html;
|
|
63
223
|
|
|
64
224
|
await this.#executeScripts();
|
|
225
|
+
if(!this.#isCurrentConnection(generation,controller)){
|
|
226
|
+
await this.#destroyImportedHost();
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
65
229
|
|
|
66
230
|
this.ready=true;
|
|
67
|
-
this.
|
|
231
|
+
const {occurrence}=this.#events.dispatch(
|
|
232
|
+
'html-import-ready',
|
|
233
|
+
Object.freeze({href,resolvedHref}),
|
|
234
|
+
{operationId,publicDetail:Object.freeze({ready:true})}
|
|
235
|
+
);
|
|
236
|
+
projectArcaneDOMEvent(this,occurrence,{
|
|
68
237
|
bubbles:true,
|
|
69
|
-
composed:true
|
|
70
|
-
|
|
71
|
-
}));
|
|
238
|
+
composed:true
|
|
239
|
+
});
|
|
72
240
|
return true;
|
|
73
241
|
}
|
|
74
242
|
|
|
75
243
|
async #executeScripts() {
|
|
76
244
|
const scripts = Array.from(this.shadowRoot.querySelectorAll('script'));
|
|
77
|
-
|
|
245
|
+
const previousDescriptor=Object.getOwnPropertyDescriptor(this,'destroy')??null;
|
|
246
|
+
const previousDestroy=this.destroy;
|
|
247
|
+
try{
|
|
248
|
+
for(const script of scripts){
|
|
78
249
|
if (script.src) {
|
|
79
250
|
console.error('ONLY INLINE SCRIPTS SUPPORTED AT THIS TIME FOR SECURITY REASONS');
|
|
80
251
|
console.warn('script src path will need to be limited to ./{text}.js, ../{text}.js), or acceptable sub folders. This can be complex.');
|
|
@@ -105,6 +276,19 @@ class HTMLImport extends HTMLElement {
|
|
|
105
276
|
htmlImportHostRegistry.delete(hostToken);
|
|
106
277
|
delete executable.dataset.arcaneHostToken;
|
|
107
278
|
}
|
|
279
|
+
}
|
|
280
|
+
}finally{
|
|
281
|
+
const installedDescriptor=Object.getOwnPropertyDescriptor(this,'destroy')??null;
|
|
282
|
+
const installedDestroy=this.destroy;
|
|
283
|
+
if(typeof installedDestroy==='function'
|
|
284
|
+
&&installedDestroy!==previousDestroy
|
|
285
|
+
&&!samePropertyDescriptor(installedDescriptor,previousDescriptor)){
|
|
286
|
+
this.#importedDestroy={
|
|
287
|
+
destroy:installedDestroy,
|
|
288
|
+
installedDescriptor,
|
|
289
|
+
previousDescriptor
|
|
290
|
+
};
|
|
291
|
+
}
|
|
108
292
|
}
|
|
109
293
|
}
|
|
110
294
|
}
|