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.
Files changed (117) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
  4. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
  5. package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
  6. package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
  7. package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
  8. package/browser-runtime/ai/browser-speech.mjs +2 -0
  9. package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
  10. package/browser-runtime/ai/model-controller.mjs +285 -95
  11. package/browser-runtime/ai/speech-worker-client.mjs +247 -32
  12. package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
  13. package/browser-runtime/event-manager.mjs +1097 -1
  14. package/docs/architecture.md +2 -2
  15. package/docs/event-manager.md +155 -27
  16. package/docs/reference/README.md +27 -27
  17. package/docs/reference/ai/browser-speech-package-authority.json +835 -0
  18. package/docs/reference/ai/browser-speech.md +1162 -246
  19. package/docs/reference/ai/browser-wasm.md +18 -7
  20. package/docs/reference/availability-and-normalization.md +6 -3
  21. package/docs/reference/behavioral-testing.md +29 -6
  22. package/docs/reference/cli.md +117 -9
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/event-manager.md +577 -32
  25. package/docs/reference/inventory/package-api.json +478 -2
  26. package/docs/reference/inventory/runtime-components.json +108 -44
  27. package/docs/reference/inventory/runtime-modules.json +131 -53
  28. package/docs/reference/mail.md +316 -0
  29. package/docs/reference/protocols.md +157 -43
  30. package/docs/reference/runtime-components.md +258 -83
  31. package/docs/reference/runtime-modules.md +613 -77
  32. package/docs/reference/sdk-api.md +1014 -25
  33. package/package.json +5 -4
  34. package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
  35. package/runtime/arcane/components/app-bar.html +34 -13
  36. package/runtime/arcane/components/assistant-panel.html +110 -57
  37. package/runtime/arcane/components/calculator.html +7 -4
  38. package/runtime/arcane/components/chart.html +58 -17
  39. package/runtime/arcane/components/chat.html +606 -136
  40. package/runtime/arcane/components/conversation-view.html +13 -6
  41. package/runtime/arcane/components/dashboard-config.html +96 -59
  42. package/runtime/arcane/components/data-maintenance.html +69 -14
  43. package/runtime/arcane/components/data-view.html +53 -7
  44. package/runtime/arcane/components/directory-picker.html +118 -32
  45. package/runtime/arcane/components/document-inspector.html +47 -10
  46. package/runtime/arcane/components/file-drop.html +81 -35
  47. package/runtime/arcane/components/file-inspector.html +72 -22
  48. package/runtime/arcane/components/file-manager.html +374 -79
  49. package/runtime/arcane/components/integration-settings.html +12 -5
  50. package/runtime/arcane/components/local-ai-status.html +48 -19
  51. package/runtime/arcane/components/markdown-document.html +161 -68
  52. package/runtime/arcane/components/markdown-editor.html +110 -33
  53. package/runtime/arcane/components/media-embed.html +8 -5
  54. package/runtime/arcane/components/modal.html +15 -5
  55. package/runtime/arcane/components/output-panel.html +28 -23
  56. package/runtime/arcane/components/preferences-form.html +22 -4
  57. package/runtime/arcane/components/record-timeline.html +18 -2
  58. package/runtime/arcane/components/relationship-board.html +23 -3
  59. package/runtime/arcane/components/screen-capture.html +10 -4
  60. package/runtime/arcane/components/source-code-viewer.html +76 -9
  61. package/runtime/arcane/components/source-explanation.html +23 -3
  62. package/runtime/arcane/components/speech.html +462 -384
  63. package/runtime/arcane/components/summary-strip.html +22 -11
  64. package/runtime/arcane/components/table.html +39 -21
  65. package/runtime/arcane/components/task-progress.html +79 -21
  66. package/runtime/arcane/components/terminal-workspace.html +7 -4
  67. package/runtime/arcane/components/theme-editor.html +7 -3
  68. package/runtime/arcane/components/unified-inbox.html +9 -4
  69. package/runtime/arcane/components/voice-transcription.html +639 -98
  70. package/runtime/arcane/components/weather-widget.html +5 -3
  71. package/runtime/arcane/components/web-navigator.html +48 -8
  72. package/runtime/arcane/entities/Chat.js +1 -1
  73. package/runtime/arcane/entities/User.js +110 -23
  74. package/runtime/arcane/modules/AI.js +2109 -130
  75. package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
  76. package/runtime/arcane/modules/AIRuntimeState.js +109 -52
  77. package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
  78. package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
  79. package/runtime/arcane/modules/CalculatorEngine.js +63 -3
  80. package/runtime/arcane/modules/CommunicationAppController.js +588 -28
  81. package/runtime/arcane/modules/CommunicationHub.js +590 -10
  82. package/runtime/arcane/modules/ComponentContracts.js +470 -0
  83. package/runtime/arcane/modules/ConversationTimebox.js +152 -33
  84. package/runtime/arcane/modules/DBLS.js +40 -7
  85. package/runtime/arcane/modules/DBOPFS.js +35 -11
  86. package/runtime/arcane/modules/DataMaintenance.js +12 -2
  87. package/runtime/arcane/modules/Errors.js +65 -7
  88. package/runtime/arcane/modules/HTMLImport.js +198 -14
  89. package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
  90. package/runtime/arcane/modules/Mail.js +738 -115
  91. package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
  92. package/runtime/arcane/modules/MailTransport.mjs +197 -39
  93. package/runtime/arcane/modules/Ollama.js +36 -1
  94. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
  95. package/runtime/arcane/modules/PreferenceStore.js +367 -33
  96. package/runtime/arcane/modules/RecordReviewStore.js +322 -23
  97. package/runtime/arcane/modules/ScreenCapture.js +1397 -15
  98. package/runtime/arcane/modules/SpeechPlayback.js +438 -41
  99. package/runtime/arcane/modules/TerminalClient.js +277 -12
  100. package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
  101. package/runtime/arcane/modules/ThemeManager.js +39 -7
  102. package/runtime/arcane/modules/TimeGuard.js +131 -20
  103. package/runtime/arcane/modules/WaitForComponent.js +386 -33
  104. package/schemas/arcane-lock.schema.json +2 -2
  105. package/src/cli/main.mjs +435 -9
  106. package/src/event-manager.mjs +1097 -1
  107. package/src/import-map.mjs +21 -3
  108. package/src/index.mjs +13 -0
  109. package/src/installed-sdk-runtime.mjs +112 -0
  110. package/src/mail-api.mjs +22 -0
  111. package/src/mail-credentials.mjs +667 -0
  112. package/src/mail-server.mjs +1769 -0
  113. package/src/mail.mjs +261 -0
  114. package/src/sdk-browser-runtime.mjs +85 -41
  115. package/src/testing-loader.mjs +7 -0
  116. package/src/toolchain.mjs +3 -0
  117. package/src/workspace.mjs +1 -1
@@ -0,0 +1,667 @@
1
+ import {spawn} from 'node:child_process';
2
+ import path from 'node:path';
3
+ import {TextDecoder} from 'node:util';
4
+ import {ArcaneError,ERROR_CODES} from './errors.mjs';
5
+
6
+ export const RESEND_CREDENTIAL_TARGET_PREFIX='ArcaneOSSDK/mail/resend/';
7
+
8
+ const CREDENTIAL_STORE='windows-credential-manager';
9
+ const MAX_PROFILE_LENGTH=64;
10
+ const MAX_CREDENTIAL_BYTES=2_560;
11
+ const MAX_HELPER_INPUT_BYTES=8_192;
12
+ const MAX_HELPER_OUTPUT_BYTES=8_192;
13
+ const DEFAULT_HELPER_TIMEOUT_MS=15_000;
14
+ const MAX_HELPER_TIMEOUT_MS=60_000;
15
+ const PROFILE_PATTERN=/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
16
+ const SECRET_PATTERN=/^[\x21-\x7e]+$/u;
17
+ const BASE64_PATTERN=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;
18
+
19
+ const WINDOWS_CREDENTIAL_HELPER=String.raw`
20
+ $ErrorActionPreference='Stop'
21
+ $ProgressPreference='SilentlyContinue'
22
+ $VerbosePreference='SilentlyContinue'
23
+ $WarningPreference='SilentlyContinue'
24
+ Set-StrictMode -Version 3.0
25
+
26
+ try {
27
+ Add-Type -Language CSharp -TypeDefinition @'
28
+ using System;
29
+ using System.ComponentModel;
30
+ using System.Runtime.InteropServices;
31
+
32
+ public static class ArcaneResendCredentialNative
33
+ {
34
+ private const UInt32 GenericCredential = 1;
35
+ private const UInt32 PersistLocalMachine = 2;
36
+ private const Int32 NotFound = 1168;
37
+ private const Int32 MaximumBlobBytes = 2560;
38
+
39
+ [StructLayout(LayoutKind.Sequential)]
40
+ private struct NativeFileTime
41
+ {
42
+ public UInt32 Low;
43
+ public UInt32 High;
44
+ }
45
+
46
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
47
+ private struct NativeCredential
48
+ {
49
+ public UInt32 Flags;
50
+ public UInt32 Type;
51
+ [MarshalAs(UnmanagedType.LPWStr)] public String TargetName;
52
+ [MarshalAs(UnmanagedType.LPWStr)] public String Comment;
53
+ public NativeFileTime LastWritten;
54
+ public UInt32 CredentialBlobSize;
55
+ public IntPtr CredentialBlob;
56
+ public UInt32 Persist;
57
+ public UInt32 AttributeCount;
58
+ public IntPtr Attributes;
59
+ [MarshalAs(UnmanagedType.LPWStr)] public String TargetAlias;
60
+ [MarshalAs(UnmanagedType.LPWStr)] public String UserName;
61
+ }
62
+
63
+ [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode,
64
+ ExactSpelling = true, SetLastError = true)]
65
+ [return: MarshalAs(UnmanagedType.Bool)]
66
+ private static extern Boolean CredWrite(ref NativeCredential credential, UInt32 flags);
67
+
68
+ [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode,
69
+ ExactSpelling = true, SetLastError = true)]
70
+ [return: MarshalAs(UnmanagedType.Bool)]
71
+ private static extern Boolean CredRead(String target, UInt32 type, UInt32 flags,
72
+ out IntPtr credential);
73
+
74
+ [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode,
75
+ ExactSpelling = true, SetLastError = true)]
76
+ [return: MarshalAs(UnmanagedType.Bool)]
77
+ private static extern Boolean CredDelete(String target, UInt32 type, UInt32 flags);
78
+
79
+ [DllImport("Advapi32.dll", EntryPoint = "CredFree", ExactSpelling = true,
80
+ SetLastError = false)]
81
+ private static extern void CredFree(IntPtr credential);
82
+
83
+ public static void Write(String target, Byte[] value)
84
+ {
85
+ if (value == null || value.Length == 0 || value.Length > MaximumBlobBytes)
86
+ throw new ArgumentException("Invalid credential value.");
87
+
88
+ GCHandle pinned = default(GCHandle);
89
+ try
90
+ {
91
+ pinned = GCHandle.Alloc(value, GCHandleType.Pinned);
92
+ NativeCredential credential = new NativeCredential();
93
+ credential.Type = GenericCredential;
94
+ credential.TargetName = target;
95
+ credential.CredentialBlobSize = checked((UInt32)value.Length);
96
+ credential.CredentialBlob = pinned.AddrOfPinnedObject();
97
+ credential.Persist = PersistLocalMachine;
98
+ credential.UserName = "Arcane OS SDK";
99
+ if (!CredWrite(ref credential, 0))
100
+ throw new Win32Exception(Marshal.GetLastWin32Error());
101
+ }
102
+ finally
103
+ {
104
+ Array.Clear(value, 0, value.Length);
105
+ if (pinned.IsAllocated) pinned.Free();
106
+ }
107
+ }
108
+
109
+ public static Byte[] Read(String target)
110
+ {
111
+ IntPtr pointer;
112
+ if (!CredRead(target, GenericCredential, 0, out pointer))
113
+ {
114
+ Int32 error = Marshal.GetLastWin32Error();
115
+ if (error == NotFound) return null;
116
+ throw new Win32Exception(error);
117
+ }
118
+
119
+ try
120
+ {
121
+ NativeCredential credential =
122
+ (NativeCredential)Marshal.PtrToStructure(pointer, typeof(NativeCredential));
123
+ if (credential.CredentialBlobSize == 0 ||
124
+ credential.CredentialBlobSize > MaximumBlobBytes)
125
+ throw new InvalidOperationException("Invalid credential size.");
126
+ Byte[] value = new Byte[credential.CredentialBlobSize];
127
+ Marshal.Copy(credential.CredentialBlob, value, 0, value.Length);
128
+ return value;
129
+ }
130
+ finally
131
+ {
132
+ CredFree(pointer);
133
+ }
134
+ }
135
+
136
+ public static Boolean Exists(String target)
137
+ {
138
+ IntPtr pointer;
139
+ if (!CredRead(target, GenericCredential, 0, out pointer))
140
+ {
141
+ Int32 error = Marshal.GetLastWin32Error();
142
+ if (error == NotFound) return false;
143
+ throw new Win32Exception(error);
144
+ }
145
+ CredFree(pointer);
146
+ return true;
147
+ }
148
+
149
+ public static Boolean Delete(String target)
150
+ {
151
+ if (CredDelete(target, GenericCredential, 0)) return true;
152
+ Int32 error = Marshal.GetLastWin32Error();
153
+ if (error == NotFound) return false;
154
+ throw new Win32Exception(error);
155
+ }
156
+ }
157
+ '@
158
+
159
+ $inputText=[Console]::In.ReadToEnd()
160
+ if ([Text.Encoding]::UTF8.GetByteCount($inputText) -gt 8192) {
161
+ throw 'Invalid helper input.'
162
+ }
163
+ $request=$inputText | ConvertFrom-Json
164
+ $operation=[String]$request.operation
165
+ $target=[String]$request.target
166
+ if ($target -notmatch '^ArcaneOSSDK/mail/resend/[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$') {
167
+ throw 'Invalid credential target.'
168
+ }
169
+
170
+ switch ($operation) {
171
+ 'set' {
172
+ $credentialBytes=$null
173
+ try {
174
+ $encoded=[String]$request.secret
175
+ if ([String]::IsNullOrEmpty($encoded)) { throw 'Invalid credential value.' }
176
+ $credentialBytes=[Convert]::FromBase64String($encoded)
177
+ if ($credentialBytes.Length -eq 0 -or $credentialBytes.Length -gt 2560) {
178
+ throw 'Invalid credential value.'
179
+ }
180
+ [ArcaneResendCredentialNative]::Write($target,$credentialBytes)
181
+ [Console]::Out.Write('{"ok":true,"configured":true}')
182
+ }
183
+ finally {
184
+ if ($null -ne $credentialBytes) {
185
+ [Array]::Clear($credentialBytes,0,$credentialBytes.Length)
186
+ }
187
+ }
188
+ break
189
+ }
190
+ 'read' {
191
+ $credentialBytes=$null
192
+ try {
193
+ $credentialBytes=[ArcaneResendCredentialNative]::Read($target)
194
+ if ($null -eq $credentialBytes) {
195
+ [Console]::Out.Write('{"ok":true,"found":false}')
196
+ }
197
+ else {
198
+ $encoded=[Convert]::ToBase64String($credentialBytes)
199
+ $response=@{ok=$true;found=$true;secret=$encoded} | ConvertTo-Json -Compress
200
+ [Console]::Out.Write($response)
201
+ }
202
+ }
203
+ finally {
204
+ if ($null -ne $credentialBytes) {
205
+ [Array]::Clear($credentialBytes,0,$credentialBytes.Length)
206
+ }
207
+ }
208
+ break
209
+ }
210
+ 'status' {
211
+ $configured=[ArcaneResendCredentialNative]::Exists($target)
212
+ $response=@{ok=$true;configured=$configured} | ConvertTo-Json -Compress
213
+ [Console]::Out.Write($response)
214
+ break
215
+ }
216
+ 'delete' {
217
+ $deleted=[ArcaneResendCredentialNative]::Delete($target)
218
+ $response=@{ok=$true;deleted=$deleted;configured=$false} | ConvertTo-Json -Compress
219
+ [Console]::Out.Write($response)
220
+ break
221
+ }
222
+ default { throw 'Invalid credential operation.' }
223
+ }
224
+ }
225
+ catch {
226
+ [Console]::Error.Write('ARCANE_CREDENTIAL_HELPER_FAILED')
227
+ [Environment]::Exit(1)
228
+ }
229
+ `;
230
+
231
+ const WINDOWS_CREDENTIAL_HELPER_COMMAND=Buffer.from(
232
+ WINDOWS_CREDENTIAL_HELPER,
233
+ 'utf16le'
234
+ ).toString('base64');
235
+
236
+ function usageError(message){
237
+ return new ArcaneError(ERROR_CODES.usage,message);
238
+ }
239
+
240
+ function unavailableError(){
241
+ return new ArcaneError(
242
+ ERROR_CODES.targetUnavailable,
243
+ 'Resend credential storage requires Windows Credential Manager.'
244
+ );
245
+ }
246
+
247
+ function operationError(){
248
+ return new ArcaneError(
249
+ ERROR_CODES.operationFailed,
250
+ 'Windows Credential Manager could not complete the Resend credential operation.'
251
+ );
252
+ }
253
+
254
+ function cancellationError(){
255
+ return new ArcaneError(
256
+ ERROR_CODES.cancelled,
257
+ 'The Resend credential operation was cancelled.',
258
+ {exitCode:130}
259
+ );
260
+ }
261
+
262
+ function assertNotAborted(signal){
263
+ if(signal?.aborted)throw cancellationError();
264
+ }
265
+
266
+ export function validateMailCredentialProfile(profile){
267
+ if(typeof profile!=='string'||profile.length>MAX_PROFILE_LENGTH
268
+ ||!PROFILE_PATTERN.test(profile)){
269
+ throw usageError(
270
+ 'A credential profile must be 1-64 lowercase letters, digits, dots, underscores, or hyphens, and must begin and end with a letter or digit.'
271
+ );
272
+ }
273
+ return profile;
274
+ }
275
+
276
+ export function mailCredentialTarget(profile){
277
+ return `${RESEND_CREDENTIAL_TARGET_PREFIX}${validateMailCredentialProfile(profile)}`;
278
+ }
279
+
280
+ function validateSecret(secret){
281
+ if(typeof secret!=='string'||!SECRET_PATTERN.test(secret)
282
+ ||Buffer.byteLength(secret,'utf8')>MAX_CREDENTIAL_BYTES){
283
+ throw usageError(
284
+ 'A Resend API key must be a nonempty printable ASCII string no larger than 2,560 bytes.'
285
+ );
286
+ }
287
+ return secret;
288
+ }
289
+
290
+ function validatePlatform(platform){
291
+ if(platform!=='win32')throw unavailableError();
292
+ }
293
+
294
+ function validateTimeout(timeoutMs){
295
+ if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>MAX_HELPER_TIMEOUT_MS){
296
+ throw usageError(
297
+ `Credential helper timeout must be an integer from 1 through ${String(MAX_HELPER_TIMEOUT_MS)} milliseconds.`
298
+ );
299
+ }
300
+ return timeoutMs;
301
+ }
302
+
303
+ function powershellExecutable(systemRoot){
304
+ if(typeof systemRoot!=='string'||systemRoot.length===0
305
+ ||systemRoot.includes('\0')||!path.win32.isAbsolute(systemRoot)){
306
+ throw unavailableError();
307
+ }
308
+ return path.win32.join(
309
+ systemRoot,
310
+ 'System32',
311
+ 'WindowsPowerShell',
312
+ 'v1.0',
313
+ 'powershell.exe'
314
+ );
315
+ }
316
+
317
+ function helperArguments(){
318
+ return [
319
+ '-NoLogo',
320
+ '-NoProfile',
321
+ '-NonInteractive',
322
+ '-ExecutionPolicy',
323
+ 'Bypass',
324
+ '-EncodedCommand',
325
+ WINDOWS_CREDENTIAL_HELPER_COMMAND
326
+ ];
327
+ }
328
+
329
+ function helperEnvironment(systemRoot,temporaryDirectory){
330
+ if(typeof temporaryDirectory!=='string'||temporaryDirectory.length===0
331
+ ||temporaryDirectory.includes('\0')||!path.win32.isAbsolute(temporaryDirectory)){
332
+ throw unavailableError();
333
+ }
334
+ return {
335
+ SystemRoot:systemRoot,
336
+ WINDIR:systemRoot,
337
+ TEMP:temporaryDirectory,
338
+ TMP:temporaryDirectory
339
+ };
340
+ }
341
+
342
+ function serializeRequest(request){
343
+ const value=JSON.stringify(request);
344
+ if(Buffer.byteLength(value,'utf8')>MAX_HELPER_INPUT_BYTES){
345
+ throw usageError('The Resend credential request is too large.');
346
+ }
347
+ return value;
348
+ }
349
+
350
+ function parseResponse(buffer){
351
+ let responseText='';
352
+ try{
353
+ responseText=buffer.toString('utf8');
354
+ const response=JSON.parse(responseText);
355
+ if(response===null||typeof response!=='object'||Array.isArray(response)
356
+ ||response.ok!==true){
357
+ throw operationError();
358
+ }
359
+ return response;
360
+ }catch{
361
+ throw operationError();
362
+ }finally{
363
+ responseText='';
364
+ buffer.fill(0);
365
+ }
366
+ }
367
+
368
+ function runCredentialProcess({
369
+ executable,
370
+ args,
371
+ spawnOptions,
372
+ stdin,
373
+ spawnImpl=spawn,
374
+ signal,
375
+ timeoutMs
376
+ }){
377
+ if(typeof spawnImpl!=='function')throw usageError('spawnImpl must be a function.');
378
+ let input=stdin;
379
+ return new Promise(function executeCredentialHelper(resolve,reject){
380
+ let child;
381
+ let settled=false;
382
+ let outputBytes=0;
383
+ let errorBytes=0;
384
+ let outputChunks=[];
385
+ let timer=null;
386
+
387
+ function wipeOutput(){
388
+ for(const chunk of outputChunks)chunk.fill(0);
389
+ outputChunks=[];
390
+ }
391
+
392
+ function removeListeners(){
393
+ clearTimeout(timer);
394
+ signal?.removeEventListener('abort',onAbort);
395
+ child?.removeListener('error',onChildError);
396
+ child?.removeListener('close',onClose);
397
+ child?.stdin?.removeListener('error',onStdinError);
398
+ child?.stdout?.removeListener('data',onStdout);
399
+ child?.stderr?.removeListener('data',onStderr);
400
+ }
401
+
402
+ function finishError(error){
403
+ if(settled)return;
404
+ settled=true;
405
+ removeListeners();
406
+ wipeOutput();
407
+ input='';
408
+ reject(error);
409
+ }
410
+
411
+ function terminate(error){
412
+ try{
413
+ child?.kill();
414
+ }catch{
415
+ // The stable credential error below intentionally omits process details.
416
+ }
417
+ finishError(error);
418
+ }
419
+
420
+ function onAbort(){
421
+ terminate(cancellationError());
422
+ }
423
+
424
+ function onTimeout(){
425
+ terminate(operationError());
426
+ }
427
+
428
+ function onChildError(){
429
+ finishError(operationError());
430
+ }
431
+
432
+ function onStdinError(){
433
+ terminate(operationError());
434
+ }
435
+
436
+ function onStdout(chunk){
437
+ const value=Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk);
438
+ outputBytes+=value.length;
439
+ if(outputBytes>MAX_HELPER_OUTPUT_BYTES){
440
+ value.fill(0);
441
+ terminate(operationError());
442
+ return;
443
+ }
444
+ outputChunks.push(value);
445
+ }
446
+
447
+ function onStderr(chunk){
448
+ errorBytes+=Buffer.byteLength(chunk);
449
+ if(errorBytes>MAX_HELPER_OUTPUT_BYTES)terminate(operationError());
450
+ }
451
+
452
+ function onClose(code){
453
+ if(settled)return;
454
+ if(code!==0){
455
+ finishError(operationError());
456
+ return;
457
+ }
458
+ const output=Buffer.concat(outputChunks,outputBytes);
459
+ wipeOutput();
460
+ settled=true;
461
+ removeListeners();
462
+ input='';
463
+ resolve(output);
464
+ }
465
+
466
+ try{
467
+ child=spawnImpl(executable,args,spawnOptions);
468
+ if(!child?.stdin||!child?.stdout||!child?.stderr){
469
+ throw operationError();
470
+ }
471
+ child.once('error',onChildError);
472
+ child.once('close',onClose);
473
+ child.stdin.once('error',onStdinError);
474
+ child.stdout.on('data',onStdout);
475
+ child.stderr.on('data',onStderr);
476
+ signal?.addEventListener('abort',onAbort,{once:true});
477
+ timer=setTimeout(onTimeout,timeoutMs);
478
+ child.stdin.end(input,'utf8');
479
+ input='';
480
+ }catch{
481
+ terminate(operationError());
482
+ }
483
+ });
484
+ }
485
+
486
+ async function runWindowsCredentialHelper(request,{
487
+ platform=process.platform,
488
+ systemRoot=process.env.SystemRoot??process.env.WINDIR,
489
+ temporaryDirectory=process.env.TEMP??process.env.TMP,
490
+ spawnImpl=spawn,
491
+ runner=runCredentialProcess,
492
+ signal,
493
+ timeoutMs=DEFAULT_HELPER_TIMEOUT_MS
494
+ }={}){
495
+ validatePlatform(platform);
496
+ assertNotAborted(signal);
497
+ if(typeof runner!=='function')throw usageError('runner must be a function.');
498
+ const executable=powershellExecutable(systemRoot);
499
+ const args=helperArguments();
500
+ const boundedTimeout=validateTimeout(timeoutMs);
501
+ let requestText=serializeRequest(request);
502
+ const invocation={
503
+ executable,
504
+ args,
505
+ spawnOptions:{
506
+ cwd:systemRoot,
507
+ env:helperEnvironment(systemRoot,temporaryDirectory),
508
+ shell:false,
509
+ windowsHide:true,
510
+ stdio:['pipe','pipe','pipe']
511
+ },
512
+ stdin:requestText,
513
+ spawnImpl,
514
+ signal,
515
+ timeoutMs:boundedTimeout
516
+ };
517
+ let output;
518
+ try{
519
+ output=await runner(invocation);
520
+ }catch(error){
521
+ if(signal?.aborted||error?.code===ERROR_CODES.cancelled
522
+ ||error?.name==='AbortError'||error?.code==='ABORT_ERR'){
523
+ throw cancellationError();
524
+ }
525
+ throw operationError();
526
+ }finally{
527
+ invocation.stdin='';
528
+ requestText='';
529
+ }
530
+ if(typeof output==='string')output=Buffer.from(output,'utf8');
531
+ if(!Buffer.isBuffer(output))throw operationError();
532
+ if(output.length>MAX_HELPER_OUTPUT_BYTES){
533
+ output.fill(0);
534
+ throw operationError();
535
+ }
536
+ return parseResponse(output);
537
+ }
538
+
539
+ async function invokeCredentialHelper(request,options){
540
+ try{
541
+ return await runWindowsCredentialHelper(request,options);
542
+ }catch(error){
543
+ if(error?.code===ERROR_CODES.cancelled)throw cancellationError();
544
+ if(error?.code===ERROR_CODES.targetUnavailable)throw unavailableError();
545
+ if(error?.code===ERROR_CODES.usage)throw error;
546
+ throw operationError();
547
+ }
548
+ }
549
+
550
+ function credentialStatus(profile,exists){
551
+ return {
552
+ profile,
553
+ provider:'resend',
554
+ storage:CREDENTIAL_STORE,
555
+ exists
556
+ };
557
+ }
558
+
559
+ function validateOptions(options){
560
+ if(options===null||typeof options!=='object'||Array.isArray(options)){
561
+ throw usageError('Mail credential options must be an object.');
562
+ }
563
+ return options;
564
+ }
565
+
566
+ function helperOptions(options){
567
+ return {
568
+ platform:options.platform,
569
+ systemRoot:options.systemRoot,
570
+ temporaryDirectory:options.temporaryDirectory,
571
+ spawnImpl:options.spawnImpl,
572
+ runner:options.runner,
573
+ signal:options.signal,
574
+ timeoutMs:options.timeoutMs
575
+ };
576
+ }
577
+
578
+ export async function setMailCredential(options={}){
579
+ validateOptions(options);
580
+ const {profile,secret}=options;
581
+ const validatedProfile=validateMailCredentialProfile(profile);
582
+ validateSecret(secret);
583
+ const target=mailCredentialTarget(validatedProfile);
584
+ const secretBytes=Buffer.from(secret,'utf8');
585
+ let encoded='';
586
+ const request={operation:'set',target,secret:''};
587
+ try{
588
+ encoded=secretBytes.toString('base64');
589
+ request.secret=encoded;
590
+ const response=await invokeCredentialHelper(
591
+ request,
592
+ helperOptions(options)
593
+ );
594
+ if(response.configured!==true)throw operationError();
595
+ return credentialStatus(validatedProfile,true);
596
+ }finally{
597
+ request.secret='';
598
+ encoded='';
599
+ secretBytes.fill(0);
600
+ }
601
+ }
602
+
603
+ function decodeSecret(value){
604
+ if(typeof value!=='string'||value.length===0||value.length%4!==0
605
+ ||!BASE64_PATTERN.test(value)){
606
+ throw operationError();
607
+ }
608
+ const bytes=Buffer.from(value,'base64');
609
+ try{
610
+ if(bytes.length===0||bytes.length>MAX_CREDENTIAL_BYTES
611
+ ||bytes.toString('base64')!==value){
612
+ throw operationError();
613
+ }
614
+ const decoded=new TextDecoder('utf-8',{fatal:true}).decode(bytes);
615
+ validateSecret(decoded);
616
+ return decoded;
617
+ }catch{
618
+ throw operationError();
619
+ }finally{
620
+ bytes.fill(0);
621
+ }
622
+ }
623
+
624
+ export async function readMailCredential(options={}){
625
+ validateOptions(options);
626
+ const validatedProfile=validateMailCredentialProfile(options.profile);
627
+ const target=mailCredentialTarget(validatedProfile);
628
+ const response=await invokeCredentialHelper(
629
+ {operation:'read',target},
630
+ helperOptions(options)
631
+ );
632
+ if(response.found===false)return null;
633
+ if(response.found!==true)throw operationError();
634
+ let encoded=response.secret;
635
+ try{
636
+ return decodeSecret(encoded);
637
+ }finally{
638
+ response.secret=null;
639
+ encoded='';
640
+ }
641
+ }
642
+
643
+ export async function getMailCredentialStatus(options={}){
644
+ validateOptions(options);
645
+ const validatedProfile=validateMailCredentialProfile(options.profile);
646
+ const target=mailCredentialTarget(validatedProfile);
647
+ const response=await invokeCredentialHelper(
648
+ {operation:'status',target},
649
+ helperOptions(options)
650
+ );
651
+ if(typeof response.configured!=='boolean')throw operationError();
652
+ return credentialStatus(validatedProfile,response.configured);
653
+ }
654
+
655
+ export async function deleteMailCredential(options={}){
656
+ validateOptions(options);
657
+ const validatedProfile=validateMailCredentialProfile(options.profile);
658
+ const target=mailCredentialTarget(validatedProfile);
659
+ const response=await invokeCredentialHelper(
660
+ {operation:'delete',target},
661
+ helperOptions(options)
662
+ );
663
+ if(typeof response.deleted!=='boolean'||response.configured!==false){
664
+ throw operationError();
665
+ }
666
+ return credentialStatus(validatedProfile,false);
667
+ }