arcane-os 0.5.17 → 0.5.19
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 +34 -1
- package/README.md +9 -9
- package/bin/arcane-test.mjs +16 -13
- package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
- package/browser-runtime/ai/browser-speech-providers.mjs +46 -43
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +158 -135
- package/browser-runtime/ai/browser-wasm.mjs +6 -3
- package/browser-runtime/ai/browser-wllama-runtime.mjs +104 -23
- package/browser-runtime/ai/model-controller.mjs +94 -91
- package/browser-runtime/ai/speech-worker-client.mjs +12 -9
- package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
- package/browser-runtime/dependencies/strong-type/index.js +207 -82
- package/browser-runtime/dependencies/strong-type/package.json +15 -6
- package/browser-runtime/dom-event-instrumentation.mjs +24 -20
- package/browser-runtime/event-manager.mjs +111 -108
- package/browser-runtime/speech-text.mjs +5 -1
- package/docs/architecture.md +1 -1
- package/docs/publishing.md +10 -4
- package/docs/reference/ai/browser-speech.md +198 -5
- package/docs/reference/ai/browser-wasm.md +29 -6
- package/docs/reference/availability-and-normalization.md +40 -0
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +20 -24
- package/docs/reference/protocols.md +1 -1
- package/docs/reference/runtime-modules.md +125 -35
- package/docs/reference/sdk-api.md +23 -7
- package/examples/wasm-ai-demo/index.html +1 -0
- package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
- package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
- package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
- package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
- package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
- package/node_modules/strong-type/README.md +42 -24
- package/node_modules/strong-type/benchmark/core.js +324 -0
- package/node_modules/strong-type/index.js +207 -82
- package/node_modules/strong-type/package.json +14 -6
- package/package.json +3 -3
- package/runtime/arcane/components/app-bar.html +5 -2
- package/runtime/arcane/components/assistant-panel.html +5 -2
- package/runtime/arcane/components/chart.html +15 -12
- package/runtime/arcane/components/chat.html +170 -148
- package/runtime/arcane/components/dashboard-config.html +6 -3
- package/runtime/arcane/components/data-view.html +4 -1
- package/runtime/arcane/components/directory-picker.html +5 -2
- package/runtime/arcane/components/document-inspector.html +6 -3
- package/runtime/arcane/components/file-drop.html +5 -2
- package/runtime/arcane/components/file-inspector.html +9 -6
- package/runtime/arcane/components/file-manager.html +16 -13
- package/runtime/arcane/components/local-ai-status.html +5 -2
- package/runtime/arcane/components/markdown-document.html +14 -11
- package/runtime/arcane/components/markdown-editor.html +4 -1
- package/runtime/arcane/components/modal.html +4 -1
- package/runtime/arcane/components/output-panel.html +6 -3
- package/runtime/arcane/components/preferences-form.html +4 -1
- package/runtime/arcane/components/record-timeline.html +8 -5
- package/runtime/arcane/components/relationship-board.html +9 -6
- package/runtime/arcane/components/source-code-viewer.html +12 -9
- package/runtime/arcane/components/source-explanation.html +8 -5
- package/runtime/arcane/components/speech.html +24 -21
- package/runtime/arcane/components/summary-strip.html +4 -1
- package/runtime/arcane/components/task-progress.html +6 -3
- package/runtime/arcane/components/terminal-workspace.html +4 -1
- package/runtime/arcane/components/voice-transcription.html +11 -8
- package/runtime/arcane/components/web-navigator.html +5 -2
- package/runtime/arcane/entities/ApiModelRecord.js +5 -2
- package/runtime/arcane/entities/Calculation.js +5 -2
- package/runtime/arcane/entities/Chat.js +33 -33
- package/runtime/arcane/entities/CommunicationMessage.js +4 -1
- package/runtime/arcane/entities/CommunicationThread.js +4 -1
- package/runtime/arcane/entities/File.js +1 -1
- package/runtime/arcane/entities/Image.js +8 -5
- package/runtime/arcane/entities/IntentEnvelope.js +20 -17
- package/runtime/arcane/entities/Preference.js +9 -6
- package/runtime/arcane/entities/Theme.js +5 -2
- package/runtime/arcane/entities/User.js +11 -11
- package/runtime/arcane/entities/Weather.js +5 -2
- package/runtime/arcane/modules/AI.js +491 -165
- package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
- package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
- package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
- package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
- package/runtime/arcane/modules/AIRuntimeState.js +29 -26
- package/runtime/arcane/modules/AnsiText.js +4 -1
- package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
- package/runtime/arcane/modules/AppDataScope.js +8 -5
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
- package/runtime/arcane/modules/AsyncBoundary.js +13 -10
- package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
- package/runtime/arcane/modules/CalculatorEngine.js +5 -2
- package/runtime/arcane/modules/ChatRecords.js +18 -15
- package/runtime/arcane/modules/CommunicationAppController.js +9 -6
- package/runtime/arcane/modules/CommunicationHub.js +9 -6
- package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
- package/runtime/arcane/modules/ComponentContracts.js +56 -53
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
- package/runtime/arcane/modules/ConversationActionItems.js +15 -12
- package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
- package/runtime/arcane/modules/ConversationTimebox.js +28 -25
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
- package/runtime/arcane/modules/DBLS.js +7 -4
- package/runtime/arcane/modules/DBOPFS.js +7 -7
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
- package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
- package/runtime/arcane/modules/DirectoryPicker.js +8 -5
- package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
- package/runtime/arcane/modules/DocumentNavigation.js +7 -4
- package/runtime/arcane/modules/Errors.js +28 -25
- package/runtime/arcane/modules/HTMLImport.js +7 -4
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
- package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
- package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
- package/runtime/arcane/modules/MD.js +1 -1
- package/runtime/arcane/modules/Mail.js +46 -43
- package/runtime/arcane/modules/MailOutbox.mjs +48 -45
- package/runtime/arcane/modules/MailTransport.mjs +29 -26
- package/runtime/arcane/modules/MemoryRecords.js +7 -4
- package/runtime/arcane/modules/MessageAdvisory.js +6 -3
- package/runtime/arcane/modules/ModelDefinition.js +7 -4
- package/runtime/arcane/modules/Ollama.js +6 -3
- package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
- package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
- package/runtime/arcane/modules/PreferenceStore.js +20 -17
- package/runtime/arcane/modules/PreparedSpeech.js +485 -0
- package/runtime/arcane/modules/Questionnaire.js +6 -3
- package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
- package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
- package/runtime/arcane/modules/RecordReviewStore.js +12 -9
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
- package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
- package/runtime/arcane/modules/ScreenCapture.js +20 -17
- package/runtime/arcane/modules/SpeechPlayback.js +29 -26
- package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
- package/runtime/arcane/modules/SystemAppearance.js +5 -2
- package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
- package/runtime/arcane/modules/TerminalClient.js +14 -11
- package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
- package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
- package/runtime/arcane/modules/ThemeManager.js +5 -2
- package/runtime/arcane/modules/TimeGuard.js +1 -1
- package/runtime/arcane/modules/ToolCallRouter.js +11 -8
- package/runtime/arcane/modules/WaitForComponent.js +19 -16
- package/runtime/arcane/modules/YouTubeMedia.js +4 -1
- package/runtime/strong-type/index.js +1276 -352
- package/runtime/strong-type/package.json +69 -45
- package/src/app-descriptor.mjs +17 -14
- package/src/application-tests.mjs +4 -1
- package/src/cli/main.mjs +8 -5
- package/src/constants.mjs +4 -1
- package/src/dev-server.mjs +8 -5
- package/src/doctor.mjs +5 -2
- package/src/dom-event-instrumentation.mjs +24 -20
- package/src/errors.mjs +7 -3
- package/src/event-manager.mjs +111 -108
- package/src/event-queue.mjs +8 -5
- package/src/events.mjs +12 -9
- package/src/import-map.mjs +35 -27
- package/src/installed-sdk-runtime.mjs +4 -1
- package/src/integrated-provider-loader.mjs +9 -6
- package/src/mail-credentials.mjs +16 -13
- package/src/mail-server.mjs +53 -50
- package/src/mail.mjs +18 -15
- package/src/native-plan.mjs +12 -9
- package/src/native-provider-loader.mjs +7 -4
- package/src/packager/core.mjs +18 -15
- package/src/process.mjs +6 -3
- package/src/release-bundle.mjs +14 -11
- package/src/runtime.mjs +4 -1
- package/src/scaffold.mjs +9 -6
- package/src/sdk-browser-runtime.mjs +4 -1
- package/src/source-server.mjs +17 -14
- package/src/targets/index.mjs +5 -2
- package/src/templates/workspace-template.mjs +13 -6
- package/src/testing-loader.mjs +7 -4
- package/src/testing.mjs +10 -7
- package/src/toolchain.mjs +13 -10
- package/src/update-check.mjs +9 -6
- package/src/workspace-operation-lock.mjs +14 -11
- package/src/workspace-runtime.mjs +4 -1
- package/src/workspace.mjs +17 -14
- package/runtime/arcane/modules/AIResponseLength.js +0 -32
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {AI_PREFERENCE_SLOT_KEYS} from './AIPreferenceTuple.js';
|
|
2
5
|
import {
|
|
3
6
|
getCoreLocalModelCatalog,
|
|
@@ -18,7 +21,7 @@ const LOCAL_SPEECH_PROVIDERS=new Set([
|
|
|
18
21
|
]);
|
|
19
22
|
|
|
20
23
|
function token(value,{uppercase=false}={}){
|
|
21
|
-
const normalized=
|
|
24
|
+
const normalized=is.string(value)
|
|
22
25
|
?value.trim()
|
|
23
26
|
:'';
|
|
24
27
|
|
|
@@ -37,7 +40,7 @@ function completeResult(value){
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
function positiveSafeInteger(value){
|
|
40
|
-
return
|
|
43
|
+
return is.safeInteger(value)&&value>0
|
|
41
44
|
?value
|
|
42
45
|
:null;
|
|
43
46
|
}
|
|
@@ -45,7 +48,7 @@ function positiveSafeInteger(value){
|
|
|
45
48
|
function preferenceSlot(preferences,index){
|
|
46
49
|
const value=preferences[index];
|
|
47
50
|
|
|
48
|
-
if(
|
|
51
|
+
if(!is.string(value)||!value.trim()){
|
|
49
52
|
throw new TypeError(
|
|
50
53
|
'The AI preference for '
|
|
51
54
|
+AI_PREFERENCE_SLOT_KEYS[index]
|
|
@@ -62,7 +65,7 @@ function preferenceSlot(preferences,index){
|
|
|
62
65
|
* tuple or choose a replacement provider.
|
|
63
66
|
*/
|
|
64
67
|
export function deriveLocalAIRequirements(preferences){
|
|
65
|
-
if(!
|
|
68
|
+
if(!is.array(preferences)||preferences.length!==AI_PREFERENCE_SLOT_KEYS.length){
|
|
66
69
|
throw new TypeError(
|
|
67
70
|
'AI preferences must contain exactly '
|
|
68
71
|
+AI_PREFERENCE_SLOT_KEYS.length
|
|
@@ -138,7 +141,7 @@ function healthText(status,names){
|
|
|
138
141
|
* aggregate ready proof.
|
|
139
142
|
*/
|
|
140
143
|
export function evaluateLocalSpeechHealth(status){
|
|
141
|
-
if(!status
|
|
144
|
+
if(!status||!is.object(status)||is.array(status)){
|
|
142
145
|
return completeResult({
|
|
143
146
|
reachable:false,
|
|
144
147
|
ready:false,
|
|
@@ -232,7 +235,7 @@ function normalizedTimeout(value){
|
|
|
232
235
|
return DEFAULT_TIMEOUT_MS;
|
|
233
236
|
}
|
|
234
237
|
|
|
235
|
-
if(!
|
|
238
|
+
if(!is.safeInteger(value)||value<1){
|
|
236
239
|
throw new RangeError('Local AI readiness timeout must be a positive integer.');
|
|
237
240
|
}
|
|
238
241
|
|
|
@@ -240,13 +243,13 @@ function normalizedTimeout(value){
|
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
async function fetchJSON(fetchImpl,url,timeoutMs){
|
|
243
|
-
if(
|
|
246
|
+
if(!is.function(fetchImpl)){
|
|
244
247
|
const error=new Error('Browser fetch is unavailable.');
|
|
245
248
|
error.code='BROWSER_FETCH_UNAVAILABLE';
|
|
246
249
|
throw error;
|
|
247
250
|
}
|
|
248
251
|
|
|
249
|
-
const controller=
|
|
252
|
+
const controller=is.function(globalThis.AbortController)
|
|
250
253
|
?new AbortController()
|
|
251
254
|
:null;
|
|
252
255
|
let timer;
|
|
@@ -275,7 +278,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
|
|
|
275
278
|
throw error;
|
|
276
279
|
}
|
|
277
280
|
|
|
278
|
-
if(
|
|
281
|
+
if(!is.function(response.text)){
|
|
279
282
|
const error=new Error('The local readiness endpoint returned an invalid response.');
|
|
280
283
|
error.code='LOCAL_AI_HEALTH_INVALID_RESPONSE';
|
|
281
284
|
throw error;
|
|
@@ -283,7 +286,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
|
|
|
283
286
|
|
|
284
287
|
const text=await response.text();
|
|
285
288
|
|
|
286
|
-
if(
|
|
289
|
+
if(!is.string(text)||!text){
|
|
287
290
|
const error=new Error('The local readiness endpoint returned an invalid response.');
|
|
288
291
|
error.code='LOCAL_AI_HEALTH_INVALID_RESPONSE';
|
|
289
292
|
throw error;
|
|
@@ -299,7 +302,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
|
|
|
299
302
|
throw error;
|
|
300
303
|
}
|
|
301
304
|
|
|
302
|
-
if(!parsed
|
|
305
|
+
if(!parsed||!is.object(parsed)||is.array(parsed)){
|
|
303
306
|
const error=new Error('The local readiness endpoint returned an invalid record.');
|
|
304
307
|
error.code='LOCAL_AI_HEALTH_INVALID_RESPONSE';
|
|
305
308
|
throw error;
|
|
@@ -316,7 +319,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
|
|
|
316
319
|
}
|
|
317
320
|
|
|
318
321
|
async function probeNativeOllama(arcane,selectedModel){
|
|
319
|
-
if(
|
|
322
|
+
if(!is.function(arcane?.localAI?.status)){
|
|
320
323
|
return completeResult({
|
|
321
324
|
ready:false,
|
|
322
325
|
model:null,
|
|
@@ -377,7 +380,7 @@ async function probeNativeOllama(arcane,selectedModel){
|
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
async function probeUserManagedLoopbackOllama(arcane,selectedModel){
|
|
380
|
-
if(
|
|
383
|
+
if(!is.function(arcane?.localAI?.status)){
|
|
381
384
|
return completeResult({
|
|
382
385
|
ready:false,
|
|
383
386
|
model:null,
|
|
@@ -457,7 +460,7 @@ function unavailableBrowserOllama(){
|
|
|
457
460
|
}
|
|
458
461
|
|
|
459
462
|
async function probeNativeSpeech(arcane){
|
|
460
|
-
if(
|
|
463
|
+
if(!is.function(arcane?.speech?.status)){
|
|
461
464
|
return completeResult({
|
|
462
465
|
...evaluateLocalSpeechHealth(null),
|
|
463
466
|
errorCode:'ARCANE_SPEECH_UNAVAILABLE'
|
|
@@ -506,7 +509,7 @@ async function resolveMode(runtime,arcane){
|
|
|
506
509
|
return runtime;
|
|
507
510
|
}
|
|
508
511
|
|
|
509
|
-
if(runtime!==undefined&&(!runtime
|
|
512
|
+
if(runtime!==undefined&&(!runtime||!is.object(runtime))){
|
|
510
513
|
throw new TypeError(
|
|
511
514
|
'Local AI runtime must be "native", "browser", '
|
|
512
515
|
+'"user-managed-loopback", or a runtime snapshot.'
|
|
@@ -515,7 +518,7 @@ async function resolveMode(runtime,arcane){
|
|
|
515
518
|
|
|
516
519
|
let snapshot=runtime;
|
|
517
520
|
|
|
518
|
-
if(snapshot===undefined&&
|
|
521
|
+
if(snapshot===undefined&&is.function(arcane?.runtime?.current)){
|
|
519
522
|
try{
|
|
520
523
|
snapshot=await arcane.runtime.current();
|
|
521
524
|
}catch{
|
|
@@ -744,14 +747,14 @@ function guidance(mode,slots){
|
|
|
744
747
|
|
|
745
748
|
function nativeRecovery(arcane,recover){
|
|
746
749
|
if(recover!==undefined){
|
|
747
|
-
if(
|
|
750
|
+
if(!is.function(recover)){
|
|
748
751
|
throw new TypeError('Local AI recovery must be a function.');
|
|
749
752
|
}
|
|
750
753
|
|
|
751
754
|
return recover;
|
|
752
755
|
}
|
|
753
756
|
|
|
754
|
-
return
|
|
757
|
+
return is.function(arcane?.localAI?.recover)
|
|
755
758
|
?arcane.localAI.recover.bind(arcane.localAI)
|
|
756
759
|
:null;
|
|
757
760
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import { arcaneLogging } from 'arcane-os/logging';
|
|
2
5
|
import {
|
|
3
6
|
checkLocalAIReadiness,
|
|
@@ -36,7 +39,7 @@ function availabilityFromReport(report={}){
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
function selectedPreferences(source){
|
|
39
|
-
return
|
|
42
|
+
return is.function(source)?source():source;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
function pendingReport(requirements){
|
|
@@ -172,7 +175,7 @@ export function createLocalAIReadinessController({
|
|
|
172
175
|
if(destroyed){
|
|
173
176
|
return;
|
|
174
177
|
}
|
|
175
|
-
if(
|
|
178
|
+
if(is.function(chat.setAIAvailability)){
|
|
176
179
|
if(chatReadyListenerPending){
|
|
177
180
|
chat.removeEventListener('chat-ready',publishAvailability);
|
|
178
181
|
}
|
|
@@ -239,7 +242,7 @@ export function createLocalAIReadinessController({
|
|
|
239
242
|
}
|
|
240
243
|
status?.present?.(latestReport);
|
|
241
244
|
applyAvailability(latestReport);
|
|
242
|
-
if(
|
|
245
|
+
if(is.function(onChange)){
|
|
243
246
|
onChange(latestReport);
|
|
244
247
|
}
|
|
245
248
|
const availability=availabilityFromReport(latestReport);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
5
|
import MailOutbox from './MailOutbox.mjs';
|
|
3
6
|
import {
|
|
@@ -44,10 +47,10 @@ function codedError(message,code,ErrorType=Error){
|
|
|
44
47
|
|
|
45
48
|
function validateMailSignal(signal){
|
|
46
49
|
if(signal===null||signal===undefined) return null;
|
|
47
|
-
if(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
if(!is.object(signal)
|
|
51
|
+
||!is.boolean(signal.aborted)
|
|
52
|
+
||!is.function(signal.addEventListener)
|
|
53
|
+
||!is.function(signal.removeEventListener)){
|
|
51
54
|
throw new TypeError('Mail signal must be an AbortSignal');
|
|
52
55
|
}
|
|
53
56
|
return signal;
|
|
@@ -122,14 +125,14 @@ function waitForMailOperation(operation,signal){
|
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
function exactNativeMailResult(value){
|
|
125
|
-
if(!value
|
|
126
|
-
|
|
128
|
+
if(!value||!is.object(value)||is.array(value)
|
|
129
|
+
||!is.string(value.requestId)
|
|
127
130
|
||!NATIVE_MAIL_REQUEST_ID_PATTERN.test(value.requestId)
|
|
128
131
|
||!Object.hasOwn(NATIVE_MAIL_RESPONSE_STATUS_CODES,value.status)
|
|
129
132
|
||value.statusCode!==NATIVE_MAIL_RESPONSE_STATUS_CODES[value.status]
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
||!is.boolean(value.sent)
|
|
134
|
+
||!is.boolean(value.partial)
|
|
135
|
+
||!is.boolean(value.uncertain)
|
|
133
136
|
||value.sent!==(value.status==='accepted')
|
|
134
137
|
||value.partial!==(value.status==='partially_accepted')
|
|
135
138
|
||value.uncertain!==(value.status==='delivery_uncertain')){
|
|
@@ -150,7 +153,7 @@ function normalizedNativeMailError(error){
|
|
|
150
153
|
const retryable=uncertain||NATIVE_MAIL_RETRYABLE_ERROR_CODES.has(error?.code);
|
|
151
154
|
if(!retryable) return error;
|
|
152
155
|
const normalized=new Error(
|
|
153
|
-
|
|
156
|
+
is.string(error?.message)&&error.message
|
|
154
157
|
?error.message
|
|
155
158
|
:uncertain
|
|
156
159
|
?'Native Arcane mail delivery has an uncertain outcome.'
|
|
@@ -162,7 +165,7 @@ function normalizedNativeMailError(error){
|
|
|
162
165
|
normalized.retryable=true;
|
|
163
166
|
normalized.uncertain=uncertain;
|
|
164
167
|
const statusCode=Number(error?.statusCode??error?.status);
|
|
165
|
-
if(
|
|
168
|
+
if(is.safeInteger(statusCode)&&statusCode>=100&&statusCode<=599){
|
|
166
169
|
normalized.statusCode=statusCode;
|
|
167
170
|
}
|
|
168
171
|
return normalized;
|
|
@@ -178,7 +181,7 @@ async function loadRequiredMailStorage(){
|
|
|
178
181
|
);
|
|
179
182
|
}
|
|
180
183
|
const storage=globalThis.dbopfs;
|
|
181
|
-
if(!storage
|
|
184
|
+
if(!storage||!is.object(storage)){
|
|
182
185
|
throw codedError(
|
|
183
186
|
'Mail outbox storage is unavailable.',
|
|
184
187
|
'MAIL_OUTBOX_STORAGE_UNAVAILABLE'
|
|
@@ -245,17 +248,17 @@ export function resolveMailConfig(
|
|
|
245
248
|
config=globalThis.arcane?.config?.mail||{},
|
|
246
249
|
{document=globalThis.document,location=globalThis.location}={}
|
|
247
250
|
){
|
|
248
|
-
const supplied=config&&
|
|
249
|
-
const appName=
|
|
251
|
+
const supplied=config&&is.object(config)&&!is.array(config)?config:{};
|
|
252
|
+
const appName=is.string(supplied.appName)&&supplied.appName.trim()
|
|
250
253
|
? supplied.appName.trim()
|
|
251
254
|
: declaredApplicationId(document);
|
|
252
255
|
return completeResult({
|
|
253
256
|
appName:ARCANE_APP_ID_PATTERN.test(appName) ? appName:'',
|
|
254
|
-
appKey:
|
|
255
|
-
endpoint:
|
|
257
|
+
appKey:is.string(supplied.appKey) ? supplied.appKey:'',
|
|
258
|
+
endpoint:is.string(supplied.endpoint)&&supplied.endpoint.trim()
|
|
256
259
|
? supplied.endpoint.trim()
|
|
257
260
|
: defaultMailEndpoint(location,supplied.baseDomain||declaredMailBaseDomain(document)),
|
|
258
|
-
requestTimeout:
|
|
261
|
+
requestTimeout:is.finite(supplied.requestTimeout)
|
|
259
262
|
? supplied.requestTimeout
|
|
260
263
|
: null,
|
|
261
264
|
});
|
|
@@ -271,7 +274,7 @@ function escapeHtml(value){
|
|
|
271
274
|
}
|
|
272
275
|
|
|
273
276
|
function clonePayload(value){
|
|
274
|
-
if(
|
|
277
|
+
if(is.function(globalThis.structuredClone)){
|
|
275
278
|
return globalThis.structuredClone(value);
|
|
276
279
|
}
|
|
277
280
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -294,7 +297,7 @@ function serializePayload(value){
|
|
|
294
297
|
let reportNonceSequence=0;
|
|
295
298
|
|
|
296
299
|
function randomReportNonce(cryptoProvider){
|
|
297
|
-
if(
|
|
300
|
+
if(is.function(cryptoProvider?.randomUUID)){
|
|
298
301
|
try{
|
|
299
302
|
const value=cryptoProvider.randomUUID();
|
|
300
303
|
if(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)){
|
|
@@ -311,12 +314,12 @@ function createReportKey(timestamp,cryptoProvider){
|
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
function normalizeRecipients(values){
|
|
314
|
-
if(!
|
|
317
|
+
if(!is.array(values)){
|
|
315
318
|
throw new TypeError('Mail recipients must be an array');
|
|
316
319
|
}
|
|
317
320
|
const recipients=[];
|
|
318
321
|
for(const value of values){
|
|
319
|
-
if(
|
|
322
|
+
if(!is.string(value)){
|
|
320
323
|
throw new TypeError('Every mail recipient must be an email address');
|
|
321
324
|
}
|
|
322
325
|
const address=value.trim().toLowerCase();
|
|
@@ -331,17 +334,17 @@ function normalizeRecipients(values){
|
|
|
331
334
|
function profileValue(user,key){
|
|
332
335
|
try{
|
|
333
336
|
const value=user?.[key];
|
|
334
|
-
return
|
|
337
|
+
return is.string(value)?value:'';
|
|
335
338
|
}catch{
|
|
336
339
|
return '';
|
|
337
340
|
}
|
|
338
341
|
}
|
|
339
342
|
|
|
340
343
|
function normalizedDeliveryResult(value){
|
|
341
|
-
if(!value
|
|
342
|
-
let status=
|
|
344
|
+
if(!value||!is.object(value)||is.array(value)) return value;
|
|
345
|
+
let status=is.string(value.status)?value.status:'';
|
|
343
346
|
if(!status&&value.sent===true) status='accepted';
|
|
344
|
-
let classification=
|
|
347
|
+
let classification=is.string(value.classification)?value.classification:'';
|
|
345
348
|
if(!classification){
|
|
346
349
|
classification={
|
|
347
350
|
accepted:'accepted',
|
|
@@ -352,9 +355,9 @@ function normalizedDeliveryResult(value){
|
|
|
352
355
|
}[status]||'';
|
|
353
356
|
}
|
|
354
357
|
const retryAfterMilliseconds=Number(value.retryAfterMs);
|
|
355
|
-
const retryAfterSeconds=
|
|
358
|
+
const retryAfterSeconds=is.safeInteger(value.retryAfterSeconds)
|
|
356
359
|
? value.retryAfterSeconds
|
|
357
|
-
:
|
|
360
|
+
: is.finite(retryAfterMilliseconds)&&retryAfterMilliseconds>0
|
|
358
361
|
? Math.ceil(retryAfterMilliseconds/1000)
|
|
359
362
|
: null;
|
|
360
363
|
return {
|
|
@@ -390,7 +393,7 @@ function safeDrainDetail(summary){
|
|
|
390
393
|
}
|
|
391
394
|
|
|
392
395
|
function normalizeMailOptions(options){
|
|
393
|
-
if(!options
|
|
396
|
+
if(!options||!is.object(options)||is.array(options)){
|
|
394
397
|
throw new TypeError('Mail options must be an object');
|
|
395
398
|
}
|
|
396
399
|
const deliver=options.deliver;
|
|
@@ -399,29 +402,29 @@ function normalizeMailOptions(options){
|
|
|
399
402
|
return globalThis.navigator?.onLine!==false;
|
|
400
403
|
};
|
|
401
404
|
const onlineTarget=options.onlineTarget===undefined
|
|
402
|
-
?
|
|
405
|
+
?is.function(globalThis.addEventListener)?globalThis:null
|
|
403
406
|
:options.onlineTarget;
|
|
404
407
|
const includeContext=options.includeContext??false;
|
|
405
408
|
const cryptoProvider=Object.hasOwn(options,'crypto')
|
|
406
409
|
?options.crypto
|
|
407
410
|
:globalThis.crypto;
|
|
408
|
-
if(deliver!==undefined
|
|
411
|
+
if(deliver!==undefined&&!is.function(deliver)){
|
|
409
412
|
throw new TypeError('Mail deliver must be a function');
|
|
410
413
|
}
|
|
411
|
-
if(
|
|
414
|
+
if(!is.function(clock)||!is.function(isOnline)){
|
|
412
415
|
throw new TypeError('Mail clock and isOnline hooks must be functions');
|
|
413
416
|
}
|
|
414
417
|
if(onlineTarget!==null&&(
|
|
415
|
-
|
|
416
|
-
|
|
418
|
+
!is.function(onlineTarget?.addEventListener)
|
|
419
|
+
||!is.function(onlineTarget?.removeEventListener)
|
|
417
420
|
)){
|
|
418
421
|
throw new TypeError('Mail onlineTarget must be an EventTarget');
|
|
419
422
|
}
|
|
420
|
-
if(
|
|
423
|
+
if(!is.boolean(includeContext)){
|
|
421
424
|
throw new TypeError('Mail includeContext must be boolean');
|
|
422
425
|
}
|
|
423
426
|
if(cryptoProvider!==null&&cryptoProvider!==undefined
|
|
424
|
-
|
|
427
|
+
&&!is.object(cryptoProvider)){
|
|
425
428
|
throw new TypeError('Mail crypto must be a Web Crypto provider');
|
|
426
429
|
}
|
|
427
430
|
return {
|
|
@@ -533,7 +536,7 @@ class Mail {
|
|
|
533
536
|
|
|
534
537
|
#assertConfigured(){
|
|
535
538
|
const hasTransport=this.#deliver!==undefined
|
|
536
|
-
||
|
|
539
|
+
||is.function(globalThis.Arcane?.mail?.send)
|
|
537
540
|
||Boolean(this.endpoint);
|
|
538
541
|
if(!this.appName||!hasTransport){
|
|
539
542
|
throw codedError('Mail transport is not configured.','MAIL_NOT_CONFIGURED');
|
|
@@ -550,8 +553,8 @@ class Mail {
|
|
|
550
553
|
#time(){
|
|
551
554
|
const value=this.#clock();
|
|
552
555
|
const milliseconds=value instanceof Date?value.getTime():Number(value);
|
|
553
|
-
if(!
|
|
554
|
-
||
|
|
556
|
+
if(!is.safeInteger(milliseconds)||milliseconds<0
|
|
557
|
+
||is.NaN(new Date(milliseconds).getTime())){
|
|
555
558
|
throw codedError('Mail clock returned an invalid time.','MAIL_CLOCK_INVALID');
|
|
556
559
|
}
|
|
557
560
|
return milliseconds;
|
|
@@ -811,20 +814,20 @@ class Mail {
|
|
|
811
814
|
|
|
812
815
|
async send(to=[], subject='', payload={}, messageStyle='', messageType='') {
|
|
813
816
|
this.#assertActive();
|
|
814
|
-
if(
|
|
817
|
+
if(!is.string(subject)){
|
|
815
818
|
throw new TypeError('Mail subject must be a string');
|
|
816
819
|
}
|
|
817
820
|
const normalizedSubject=subject;
|
|
818
|
-
if(!payload
|
|
821
|
+
if(!payload||!is.object(payload)||is.array(payload)){
|
|
819
822
|
throw new TypeError('Mail payload must be an object');
|
|
820
823
|
}
|
|
821
|
-
if(
|
|
824
|
+
if(!is.string(messageStyle)){
|
|
822
825
|
throw new TypeError('Mail message style must be a string');
|
|
823
826
|
}
|
|
824
|
-
if(!
|
|
827
|
+
if(!is.array(to)){
|
|
825
828
|
throw new TypeError('Mail recipients must be an array');
|
|
826
829
|
}
|
|
827
|
-
if(
|
|
830
|
+
if(!is.string(messageType)||!MAIL_TYPES.has(messageType)){
|
|
828
831
|
throw new TypeError('Mail type must be error, report, or crisis_detected');
|
|
829
832
|
}
|
|
830
833
|
|