arcane-os 0.2.2 → 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 +29 -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 +27 -27
- 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 +6 -3
- package/docs/reference/behavioral-testing.md +29 -6
- package/docs/reference/cli.md +117 -9
- 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 +108 -44
- package/docs/reference/inventory/runtime-modules.json +131 -53
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +157 -43
- package/docs/reference/runtime-components.md +258 -83
- package/docs/reference/runtime-modules.md +613 -77
- 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 +462 -384
- 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 +639 -98
- 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 +470 -0
- 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
|
@@ -1,16 +1,596 @@
|
|
|
1
|
+
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
1
2
|
import CommunicationMessage from '../entities/CommunicationMessage.js';
|
|
2
3
|
import CommunicationThread from '../entities/CommunicationThread.js';
|
|
3
4
|
import CommunicationProviderRegistry from './CommunicationProviderRegistry.js?v=2';
|
|
4
5
|
|
|
6
|
+
export const COMMUNICATION_HUB_EVENTS=Object.freeze({
|
|
7
|
+
refreshCancelled:'communications-refresh-cancelled',
|
|
8
|
+
refreshCompleted:'communications-refresh-completed',
|
|
9
|
+
refreshFailed:'communications-refresh-failed',
|
|
10
|
+
refreshLegacy:'communications-refresh',
|
|
11
|
+
refreshPartiallyCompleted:'communications-refresh-partially-completed',
|
|
12
|
+
refreshStarted:'communications-refresh-started'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const COMMUNICATION_HUB_REFRESH_STATES=Object.freeze({
|
|
16
|
+
cancelled:'refresh-cancelled',
|
|
17
|
+
completed:'refresh-completed',
|
|
18
|
+
failed:'refresh-failed',
|
|
19
|
+
partiallyCompleted:'refresh-partially-completed',
|
|
20
|
+
started:'refresh-started'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const COMMUNICATION_HUB_REFRESH_REASONS=Object.freeze({
|
|
24
|
+
allProviderThreadListsRejected:'all-provider-thread-lists-rejected',
|
|
25
|
+
boundaryThrew:'refresh-boundary-threw',
|
|
26
|
+
hubDisposed:'hub-disposed',
|
|
27
|
+
providerThreadListRejected:'provider-thread-list-rejected',
|
|
28
|
+
signalAborted:'refresh-signal-aborted',
|
|
29
|
+
startCancelled:'refresh-start-cancelled',
|
|
30
|
+
superseded:'refresh-superseded'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const COMMUNICATION_HUB_ERROR_CODES=Object.freeze({
|
|
34
|
+
allProviderThreadListsRejected:'ARCANE_COMMUNICATION_PROVIDER_THREAD_LISTS_REJECTED',
|
|
35
|
+
disposed:'ARCANE_COMMUNICATION_HUB_DISPOSED',
|
|
36
|
+
providerThreadListRejected:'ARCANE_COMMUNICATION_PROVIDER_THREAD_LIST_REJECTED',
|
|
37
|
+
refreshAborted:'ARCANE_COMMUNICATION_HUB_REFRESH_ABORTED',
|
|
38
|
+
refreshFailed:'ARCANE_COMMUNICATION_HUB_REFRESH_FAILED',
|
|
39
|
+
refreshOptionsInvalid:'ARCANE_COMMUNICATION_HUB_REFRESH_OPTIONS_INVALID',
|
|
40
|
+
refreshSuperseded:'ARCANE_COMMUNICATION_HUB_REFRESH_SUPERSEDED'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function codedError(message,code,{cause,name='Error',ErrorType=Error}={}){
|
|
44
|
+
const error=new ErrorType(message);
|
|
45
|
+
error.name=name;
|
|
46
|
+
error.code=code;
|
|
47
|
+
if(cause!==undefined){
|
|
48
|
+
error.cause=cause;
|
|
49
|
+
}
|
|
50
|
+
return error;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function disposedError(){
|
|
54
|
+
return codedError(
|
|
55
|
+
'The communication hub has been disposed.',
|
|
56
|
+
COMMUNICATION_HUB_ERROR_CODES.disposed
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function refreshAbortError(reason,code=COMMUNICATION_HUB_ERROR_CODES.refreshAborted){
|
|
61
|
+
if(reason instanceof Error&&reason.name==='AbortError'&&reason.code===code){
|
|
62
|
+
return reason;
|
|
63
|
+
}
|
|
64
|
+
return codedError(
|
|
65
|
+
code===COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
|
|
66
|
+
?'The communication refresh was superseded by a newer refresh.'
|
|
67
|
+
:'The communication refresh was aborted.',
|
|
68
|
+
code,
|
|
69
|
+
{
|
|
70
|
+
cause:reason,
|
|
71
|
+
name:'AbortError'
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function refreshFailureError(error){
|
|
77
|
+
if(error instanceof Error&&error.code===COMMUNICATION_HUB_ERROR_CODES.refreshFailed){
|
|
78
|
+
return error;
|
|
79
|
+
}
|
|
80
|
+
return codedError(
|
|
81
|
+
'The communication refresh boundary failed.',
|
|
82
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshFailed,
|
|
83
|
+
{cause:error}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isAbortSignal(value){
|
|
88
|
+
return Boolean(value)
|
|
89
|
+
&&typeof value==='object'
|
|
90
|
+
&&typeof value.aborted==='boolean'
|
|
91
|
+
&&typeof value.addEventListener==='function'
|
|
92
|
+
&&typeof value.removeEventListener==='function';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeRefreshOptions(value){
|
|
96
|
+
if(value===undefined){
|
|
97
|
+
return Object.freeze({signal:null});
|
|
98
|
+
}
|
|
99
|
+
if(!value||typeof value!=='object'||Array.isArray(value)){
|
|
100
|
+
throw codedError(
|
|
101
|
+
'Communication refresh options must be a plain record.',
|
|
102
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshOptionsInvalid,
|
|
103
|
+
{ErrorType:TypeError}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const descriptors=Object.getOwnPropertyDescriptors(value);
|
|
107
|
+
const unknown=Reflect.ownKeys(descriptors).find(
|
|
108
|
+
function findUnknownRefreshOption(key){
|
|
109
|
+
return key!=='signal';
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
if(unknown!==undefined||Object.values(descriptors).some(
|
|
113
|
+
function findRefreshOptionAccessor(descriptor){
|
|
114
|
+
return !Object.hasOwn(descriptor,'value');
|
|
115
|
+
}
|
|
116
|
+
)){
|
|
117
|
+
throw codedError(
|
|
118
|
+
'Communication refresh options may contain only a data signal property.',
|
|
119
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshOptionsInvalid,
|
|
120
|
+
{ErrorType:TypeError}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const signal=descriptors.signal?.value??null;
|
|
124
|
+
if(signal!==null&&!isAbortSignal(signal)){
|
|
125
|
+
throw codedError(
|
|
126
|
+
'Communication refresh signal must be an AbortSignal.',
|
|
127
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshOptionsInvalid,
|
|
128
|
+
{ErrorType:TypeError}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return Object.freeze({signal});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeProviderThreads(provider,values){
|
|
135
|
+
if(!Array.isArray(values)){
|
|
136
|
+
throw new TypeError(`Communication provider ${provider.id} must return a thread array.`);
|
|
137
|
+
}
|
|
138
|
+
return values.map(function normalizeProviderThread(value){
|
|
139
|
+
return value instanceof CommunicationThread
|
|
140
|
+
?value
|
|
141
|
+
:new CommunicationThread(
|
|
142
|
+
{
|
|
143
|
+
...value,
|
|
144
|
+
providerId:provider.id
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function compareThreadsByUpdatedAt(left,right){
|
|
151
|
+
return right.updatedAt.localeCompare(left.updatedAt);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function providerFailure(provider){
|
|
155
|
+
return Object.freeze({
|
|
156
|
+
code:COMMUNICATION_HUB_ERROR_CODES.providerThreadListRejected,
|
|
157
|
+
providerId:provider.id,
|
|
158
|
+
reason:COMMUNICATION_HUB_REFRESH_REASONS.providerThreadListRejected
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function settleWithAbort(operation,promise){
|
|
163
|
+
const signal=operation.controller.signal;
|
|
164
|
+
if(signal.aborted){
|
|
165
|
+
return Promise.reject(signal.reason);
|
|
166
|
+
}
|
|
167
|
+
return new Promise(function settleCommunicationRefresh(resolve,reject){
|
|
168
|
+
let settled=false;
|
|
169
|
+
|
|
170
|
+
function cleanup(){
|
|
171
|
+
signal.removeEventListener('abort',abortRefreshSettlement);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function resolveRefreshSettlement(value){
|
|
175
|
+
if(settled){
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
settled=true;
|
|
179
|
+
cleanup();
|
|
180
|
+
resolve(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function rejectRefreshSettlement(error){
|
|
184
|
+
if(settled){
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
settled=true;
|
|
188
|
+
cleanup();
|
|
189
|
+
reject(error);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function abortRefreshSettlement(){
|
|
193
|
+
rejectRefreshSettlement(signal.reason);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
signal.addEventListener('abort',abortRefreshSettlement,{once:true});
|
|
197
|
+
promise.then(resolveRefreshSettlement,rejectRefreshSettlement);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
5
201
|
export default class CommunicationHub extends EventTarget{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
202
|
+
#disposed=false;
|
|
203
|
+
#events;
|
|
204
|
+
#operationSequence=0;
|
|
205
|
+
#refreshOperation=null;
|
|
206
|
+
|
|
207
|
+
constructor({providers=[],enabledProviderIds=[]}={}){
|
|
208
|
+
super();
|
|
209
|
+
this.registry=providers instanceof CommunicationProviderRegistry
|
|
210
|
+
?providers
|
|
211
|
+
:new CommunicationProviderRegistry(providers);
|
|
212
|
+
this.enabled=new Set(enabledProviderIds);
|
|
213
|
+
this.threads=[];
|
|
214
|
+
this.#events=createArcaneEventSource(
|
|
215
|
+
this,
|
|
216
|
+
{
|
|
217
|
+
source:'communication-hub',
|
|
218
|
+
eventTypes:Object.freeze(Object.values(COMMUNICATION_HUB_EVENTS))
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
addEventListener(type,listener,options){
|
|
224
|
+
return this.#events.addEventListener(type,listener,options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
removeEventListener(type,listener,options){
|
|
228
|
+
return this.#events.removeEventListener(type,listener,options);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
on(type,listener,options){
|
|
232
|
+
return this.#events.on(type,listener,options);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
dispatchEvent(value){
|
|
236
|
+
return this.#events.dispatchEvent(value);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#assertOpen(){
|
|
240
|
+
if(this.#disposed){
|
|
241
|
+
throw disposedError();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#operationId(){
|
|
246
|
+
this.#operationSequence+=1;
|
|
247
|
+
return `${this.#events.instanceId}:refresh:${this.#operationSequence.toString(36)}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#clearRefreshOperation(operation){
|
|
251
|
+
operation.signal?.removeEventListener('abort',operation.abortFromSignal);
|
|
252
|
+
if(this.#refreshOperation===operation){
|
|
253
|
+
this.#refreshOperation=null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#publishCancellation(operation,error,reason){
|
|
258
|
+
if(operation.terminal){
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
operation.terminal=true;
|
|
262
|
+
operation.error=error;
|
|
263
|
+
this.#clearRefreshOperation(operation);
|
|
264
|
+
operation.controller.abort(error);
|
|
265
|
+
if(!this.#events.disposed){
|
|
266
|
+
this.#events.dispatch(
|
|
267
|
+
COMMUNICATION_HUB_EVENTS.refreshCancelled,
|
|
268
|
+
Object.freeze({error,reason}),
|
|
269
|
+
{
|
|
270
|
+
operationId:operation.operationId,
|
|
271
|
+
publicDetail:Object.freeze({
|
|
272
|
+
code:error.code,
|
|
273
|
+
reason,
|
|
274
|
+
state:COMMUNICATION_HUB_REFRESH_STATES.cancelled
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#cancelActiveRefresh(error,reason){
|
|
283
|
+
const operation=this.#refreshOperation;
|
|
284
|
+
if(!operation){
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return this.#publishCancellation(operation,error,reason);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
setEnabled(ids=[]){
|
|
291
|
+
this.#assertOpen();
|
|
292
|
+
this.enabled=new Set(Array.from(ids,String));
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
enabledProviders(){
|
|
297
|
+
this.#assertOpen();
|
|
298
|
+
return this.registry.list().filter(
|
|
299
|
+
function selectEnabledCommunicationProvider(provider){
|
|
300
|
+
return this.enabled.has(provider.id);
|
|
301
|
+
},
|
|
302
|
+
this
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async refresh(options={}){
|
|
307
|
+
this.#assertOpen();
|
|
308
|
+
const settings=normalizeRefreshOptions(options);
|
|
309
|
+
if(settings.signal?.aborted){
|
|
310
|
+
throw refreshAbortError(settings.signal.reason);
|
|
311
|
+
}
|
|
312
|
+
this.#cancelActiveRefresh(
|
|
313
|
+
refreshAbortError(
|
|
314
|
+
undefined,
|
|
315
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
|
|
316
|
+
),
|
|
317
|
+
COMMUNICATION_HUB_REFRESH_REASONS.superseded
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const providers=this.enabledProviders();
|
|
321
|
+
const controller=new AbortController();
|
|
322
|
+
const operation={
|
|
323
|
+
abortFromSignal:null,
|
|
324
|
+
controller,
|
|
325
|
+
error:null,
|
|
326
|
+
operationId:this.#operationId(),
|
|
327
|
+
signal:settings.signal,
|
|
328
|
+
terminal:false
|
|
329
|
+
};
|
|
330
|
+
operation.abortFromSignal=function abortCommunicationRefreshFromSignal(){
|
|
331
|
+
this.#publishCancellation(
|
|
332
|
+
operation,
|
|
333
|
+
refreshAbortError(settings.signal.reason),
|
|
334
|
+
COMMUNICATION_HUB_REFRESH_REASONS.signalAborted
|
|
335
|
+
);
|
|
336
|
+
}.bind(this);
|
|
337
|
+
settings.signal?.addEventListener(
|
|
338
|
+
'abort',
|
|
339
|
+
operation.abortFromSignal,
|
|
340
|
+
{once:true}
|
|
341
|
+
);
|
|
342
|
+
this.#refreshOperation=operation;
|
|
343
|
+
|
|
344
|
+
const providerIds=Object.freeze(providers.map(
|
|
345
|
+
function communicationProviderId(provider){
|
|
346
|
+
return provider.id;
|
|
347
|
+
}
|
|
348
|
+
));
|
|
349
|
+
const startPublication=this.#events.dispatch(
|
|
350
|
+
COMMUNICATION_HUB_EVENTS.refreshStarted,
|
|
351
|
+
Object.freeze({providerIds}),
|
|
352
|
+
{
|
|
353
|
+
cancelable:true,
|
|
354
|
+
operationId:operation.operationId,
|
|
355
|
+
publicDetail:Object.freeze({
|
|
356
|
+
providerCount:providerIds.length,
|
|
357
|
+
providerIds,
|
|
358
|
+
state:COMMUNICATION_HUB_REFRESH_STATES.started
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
if(operation.terminal){
|
|
363
|
+
throw operation.error;
|
|
364
|
+
}
|
|
365
|
+
if(!startPublication.accepted){
|
|
366
|
+
const error=refreshAbortError();
|
|
367
|
+
this.#publishCancellation(
|
|
368
|
+
operation,
|
|
369
|
+
error,
|
|
370
|
+
COMMUNICATION_HUB_REFRESH_REASONS.startCancelled
|
|
371
|
+
);
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
this.#assertOpen();
|
|
375
|
+
if(this.#refreshOperation!==operation){
|
|
376
|
+
throw operation.error??refreshAbortError(
|
|
377
|
+
undefined,
|
|
378
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const providerOperations=providers.map(
|
|
383
|
+
function startCommunicationProviderRefresh(provider){
|
|
384
|
+
return Promise.resolve().then(
|
|
385
|
+
function requestProviderThreads(){
|
|
386
|
+
if(controller.signal.aborted){
|
|
387
|
+
throw controller.signal.reason;
|
|
388
|
+
}
|
|
389
|
+
return provider.listThreads(
|
|
390
|
+
{
|
|
391
|
+
operationId:operation.operationId,
|
|
392
|
+
signal:controller.signal
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
).then(
|
|
397
|
+
function normalizeCommunicationProviderThreads(values){
|
|
398
|
+
return normalizeProviderThreads(provider,values);
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
const providerSettlements=Promise.allSettled(providerOperations);
|
|
404
|
+
|
|
405
|
+
try{
|
|
406
|
+
const results=await settleWithAbort(operation,providerSettlements);
|
|
407
|
+
if(controller.signal.aborted){
|
|
408
|
+
throw controller.signal.reason;
|
|
409
|
+
}
|
|
410
|
+
this.#assertOpen();
|
|
411
|
+
if(this.#refreshOperation!==operation){
|
|
412
|
+
throw operation.error??refreshAbortError(
|
|
413
|
+
undefined,
|
|
414
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const errors=[];
|
|
419
|
+
const failures=[];
|
|
420
|
+
const threads=[];
|
|
421
|
+
for(let index=0;index<results.length;index+=1){
|
|
422
|
+
const result=results[index];
|
|
423
|
+
if(result.status==='fulfilled'){
|
|
424
|
+
threads.push(...result.value);
|
|
425
|
+
}else{
|
|
426
|
+
errors.push(result.reason);
|
|
427
|
+
failures.push(providerFailure(providers[index]));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
threads.sort(compareThreadsByUpdatedAt);
|
|
431
|
+
if(controller.signal.aborted){
|
|
432
|
+
throw controller.signal.reason;
|
|
433
|
+
}
|
|
434
|
+
this.#assertOpen();
|
|
435
|
+
if(this.#refreshOperation!==operation){
|
|
436
|
+
throw operation.error??refreshAbortError(
|
|
437
|
+
undefined,
|
|
438
|
+
COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.threads=threads;
|
|
443
|
+
const returnedThreads=[...this.threads];
|
|
444
|
+
const returnedErrors=[...errors];
|
|
445
|
+
const frozenFailures=Object.freeze([...failures]);
|
|
446
|
+
const allProvidersRejected=providers.length>0
|
|
447
|
+
&&failures.length===providers.length;
|
|
448
|
+
const state=allProvidersRejected
|
|
449
|
+
?COMMUNICATION_HUB_REFRESH_STATES.failed
|
|
450
|
+
:failures.length
|
|
451
|
+
?COMMUNICATION_HUB_REFRESH_STATES.partiallyCompleted
|
|
452
|
+
:COMMUNICATION_HUB_REFRESH_STATES.completed;
|
|
453
|
+
const terminalType=allProvidersRejected
|
|
454
|
+
?COMMUNICATION_HUB_EVENTS.refreshFailed
|
|
455
|
+
:failures.length
|
|
456
|
+
?COMMUNICATION_HUB_EVENTS.refreshPartiallyCompleted
|
|
457
|
+
:COMMUNICATION_HUB_EVENTS.refreshCompleted;
|
|
458
|
+
const compatibilityDetail=Object.freeze({
|
|
459
|
+
errors:Object.freeze([...returnedErrors]),
|
|
460
|
+
failures:frozenFailures,
|
|
461
|
+
state,
|
|
462
|
+
threads:Object.freeze([...returnedThreads])
|
|
463
|
+
});
|
|
464
|
+
const publicDetail=Object.freeze({
|
|
465
|
+
...(allProvidersRejected
|
|
466
|
+
?{
|
|
467
|
+
code:COMMUNICATION_HUB_ERROR_CODES.allProviderThreadListsRejected,
|
|
468
|
+
reason:COMMUNICATION_HUB_REFRESH_REASONS.allProviderThreadListsRejected
|
|
469
|
+
}
|
|
470
|
+
:{}),
|
|
471
|
+
failureCount:failures.length,
|
|
472
|
+
failures:frozenFailures,
|
|
473
|
+
providerCount:providers.length,
|
|
474
|
+
state,
|
|
475
|
+
threadCount:threads.length
|
|
476
|
+
});
|
|
477
|
+
operation.terminal=true;
|
|
478
|
+
this.#clearRefreshOperation(operation);
|
|
479
|
+
this.#events.dispatch(
|
|
480
|
+
terminalType,
|
|
481
|
+
compatibilityDetail,
|
|
482
|
+
{
|
|
483
|
+
operationId:operation.operationId,
|
|
484
|
+
publicDetail
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
if(!this.#events.disposed){
|
|
488
|
+
this.#events.dispatch(
|
|
489
|
+
COMMUNICATION_HUB_EVENTS.refreshLegacy,
|
|
490
|
+
Object.freeze({
|
|
491
|
+
errors:compatibilityDetail.errors,
|
|
492
|
+
threads:compatibilityDetail.threads
|
|
493
|
+
}),
|
|
494
|
+
{
|
|
495
|
+
operationId:operation.operationId,
|
|
496
|
+
publicDetail
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
return {threads:returnedThreads,errors:returnedErrors};
|
|
501
|
+
}catch(error){
|
|
502
|
+
if(operation.terminal){
|
|
503
|
+
throw operation.error??error;
|
|
504
|
+
}
|
|
505
|
+
if(error instanceof Error&&error.name==='AbortError'){
|
|
506
|
+
this.#publishCancellation(
|
|
507
|
+
operation,
|
|
508
|
+
error,
|
|
509
|
+
error.code===COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
|
|
510
|
+
?COMMUNICATION_HUB_REFRESH_REASONS.superseded
|
|
511
|
+
:COMMUNICATION_HUB_REFRESH_REASONS.signalAborted
|
|
512
|
+
);
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
const failure=refreshFailureError(error);
|
|
516
|
+
operation.terminal=true;
|
|
517
|
+
operation.error=failure;
|
|
518
|
+
this.#clearRefreshOperation(operation);
|
|
519
|
+
if(!this.#events.disposed){
|
|
520
|
+
this.#events.dispatch(
|
|
521
|
+
COMMUNICATION_HUB_EVENTS.refreshFailed,
|
|
522
|
+
Object.freeze({error:failure}),
|
|
523
|
+
{
|
|
524
|
+
operationId:operation.operationId,
|
|
525
|
+
publicDetail:Object.freeze({
|
|
526
|
+
code:failure.code,
|
|
527
|
+
reason:COMMUNICATION_HUB_REFRESH_REASONS.boundaryThrew,
|
|
528
|
+
state:COMMUNICATION_HUB_REFRESH_STATES.failed
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
throw failure;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async messages(thread){
|
|
538
|
+
this.#assertOpen();
|
|
539
|
+
const record=thread instanceof CommunicationThread
|
|
540
|
+
?thread
|
|
541
|
+
:new CommunicationThread(thread);
|
|
542
|
+
const values=await this.registry.get(record.providerId).getMessages(record.id);
|
|
543
|
+
this.#assertOpen();
|
|
544
|
+
return values.map(function normalizeCommunicationMessage(value){
|
|
545
|
+
return value instanceof CommunicationMessage
|
|
546
|
+
?value
|
|
547
|
+
:new CommunicationMessage(
|
|
548
|
+
{
|
|
549
|
+
...value,
|
|
550
|
+
threadId:record.id,
|
|
551
|
+
providerId:record.providerId,
|
|
552
|
+
channel:record.channel
|
|
553
|
+
}
|
|
554
|
+
);
|
|
555
|
+
}).sort(function compareCommunicationMessages(left,right){
|
|
556
|
+
return left.timestamp.localeCompare(right.timestamp);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async send({providerId,threadId,channel,body,subject='',recipients=[]}={}){
|
|
561
|
+
this.#assertOpen();
|
|
562
|
+
if(!String(body||'').trim()){
|
|
563
|
+
throw new TypeError('A message body is required.');
|
|
564
|
+
}
|
|
565
|
+
const result=await this.registry.get(providerId).send(
|
|
566
|
+
{
|
|
567
|
+
threadId,
|
|
568
|
+
channel,
|
|
569
|
+
body:String(body),
|
|
570
|
+
subject:String(subject),
|
|
571
|
+
recipients:Array.from(recipients||[])
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
this.#assertOpen();
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
dispose(){
|
|
579
|
+
if(this.#disposed){
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
this.#disposed=true;
|
|
583
|
+
this.#cancelActiveRefresh(
|
|
584
|
+
refreshAbortError(
|
|
585
|
+
disposedError(),
|
|
586
|
+
COMMUNICATION_HUB_ERROR_CODES.disposed
|
|
587
|
+
),
|
|
588
|
+
COMMUNICATION_HUB_REFRESH_REASONS.hubDisposed
|
|
589
|
+
);
|
|
590
|
+
return this.#events.dispose();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
destroy(){
|
|
594
|
+
return this.dispose();
|
|
595
|
+
}
|
|
16
596
|
}
|