@trops/dash-core 0.1.501 → 0.1.503
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/dist/electron/index.js +1649 -1266
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +39 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +39 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var require$$0$1 = require('electron');
|
|
4
|
-
var require$$1$1 = require('
|
|
5
|
-
var require$$1$2 = require('path');
|
|
4
|
+
var require$$1$1 = require('path');
|
|
6
5
|
var require$$0$2 = require('fs');
|
|
7
|
-
var require$$
|
|
8
|
-
var require$$1$
|
|
6
|
+
var require$$8$1 = require('objects-to-csv');
|
|
7
|
+
var require$$1$2 = require('readline');
|
|
9
8
|
var require$$2 = require('xtreamer');
|
|
10
9
|
var require$$3$1 = require('xml2js');
|
|
11
10
|
var require$$4 = require('JSONStream');
|
|
12
11
|
var require$$5 = require('stream');
|
|
13
12
|
var require$$6 = require('csv-parser');
|
|
14
13
|
var require$$0$3 = require('quickjs-emscripten');
|
|
15
|
-
var require$$
|
|
14
|
+
var require$$10 = require('https');
|
|
16
15
|
var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
|
|
17
|
-
var require$$1$
|
|
16
|
+
var require$$1$3 = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
18
17
|
var require$$0$4 = require('pkce-challenge');
|
|
19
18
|
var require$$2$1 = require('os');
|
|
20
19
|
var require$$12 = require('child_process');
|
|
20
|
+
var require$$0$6 = require('electron-store');
|
|
21
21
|
var require$$3$2 = require('adm-zip');
|
|
22
22
|
var require$$4$1 = require('url');
|
|
23
23
|
var require$$2$2 = require('vm');
|
|
24
|
-
var require$$1$
|
|
24
|
+
var require$$1$4 = require('croner');
|
|
25
25
|
var require$$2$3 = require('algoliasearch');
|
|
26
26
|
var require$$3$3 = require('node:path');
|
|
27
|
-
var require$$0$
|
|
27
|
+
var require$$0$7 = require('openai');
|
|
28
28
|
require('live-plugin-manager');
|
|
29
|
-
var require$$0$
|
|
29
|
+
var require$$0$a = require('@anthropic-ai/sdk');
|
|
30
30
|
var require$$3$4 = require('crypto');
|
|
31
31
|
var require$$8$2 = require('zod');
|
|
32
|
-
var require$$0$
|
|
33
|
-
var require$$1$
|
|
32
|
+
var require$$0$8 = require('http');
|
|
33
|
+
var require$$1$5 = require('http2');
|
|
34
34
|
var require$$2$4 = require('node-forge');
|
|
35
|
-
var require$$0$
|
|
36
|
-
var require$$1$
|
|
37
|
-
var require$$0$
|
|
35
|
+
var require$$0$9 = require('css');
|
|
36
|
+
var require$$1$6 = require('node-vibrant/node');
|
|
37
|
+
var require$$0$b = require('ws');
|
|
38
38
|
|
|
39
39
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
40
40
|
|
|
@@ -79,11 +79,11 @@ const SECURE_STORAGE_ENCRYPT_STRING_COMPLETE =
|
|
|
79
79
|
const SECURE_STORAGE_ENCRYPT_STRING_ERROR =
|
|
80
80
|
"secure-storage-encrypt-string-error";
|
|
81
81
|
|
|
82
|
-
const SECURE_STORE_GET_DATA
|
|
82
|
+
const SECURE_STORE_GET_DATA = "secure-storage-get-data";
|
|
83
83
|
const SECURE_STORE_GET_DATA_COMPLETE = "secure-storage-get-data-complete";
|
|
84
84
|
const SECURE_STORE_GET_DATA_ERROR = "secure-storage-get-data-error";
|
|
85
85
|
|
|
86
|
-
const SECURE_STORE_SET_DATA
|
|
86
|
+
const SECURE_STORE_SET_DATA = "secure-storage-set-data";
|
|
87
87
|
const SECURE_STORE_SET_DATA_COMPLETE = "secure-storage-set-data-complete";
|
|
88
88
|
const SECURE_STORE_SET_DATA_ERROR = "secure-storage-set-data-error";
|
|
89
89
|
|
|
@@ -91,10 +91,10 @@ var secureStorageEvents$1 = {
|
|
|
91
91
|
SECURE_STORE_ENCRYPTION_CHECK: SECURE_STORE_ENCRYPTION_CHECK$1,
|
|
92
92
|
SECURE_STORE_ENCRYPTION_CHECK_COMPLETE,
|
|
93
93
|
SECURE_STORE_ENCRYPTION_CHECK_ERROR,
|
|
94
|
-
SECURE_STORE_SET_DATA
|
|
94
|
+
SECURE_STORE_SET_DATA,
|
|
95
95
|
SECURE_STORE_SET_DATA_COMPLETE,
|
|
96
96
|
SECURE_STORE_SET_DATA_ERROR,
|
|
97
|
-
SECURE_STORE_GET_DATA
|
|
97
|
+
SECURE_STORE_GET_DATA,
|
|
98
98
|
SECURE_STORE_GET_DATA_COMPLETE,
|
|
99
99
|
SECURE_STORE_GET_DATA_ERROR,
|
|
100
100
|
SECURE_STORAGE_ENCRYPT_STRING,
|
|
@@ -1044,22 +1044,23 @@ var dialogController$1 = {
|
|
|
1044
1044
|
};
|
|
1045
1045
|
|
|
1046
1046
|
/**
|
|
1047
|
-
*
|
|
1047
|
+
* secureStoreController
|
|
1048
|
+
*
|
|
1049
|
+
* Thin wrapper around Electron's `safeStorage` for renderer-side
|
|
1050
|
+
* encryption checks. The `saveData` / `getData` helpers that previously
|
|
1051
|
+
* lived here were unwired (no IPC handler in dash-electron) and
|
|
1052
|
+
* lacked per-widget scoping; they were removed alongside their
|
|
1053
|
+
* widget-facing API entries. See `electron/api/secureStoreApi.js`
|
|
1054
|
+
* and the regression-pin in `secureStoreApi.test.js`.
|
|
1055
|
+
*
|
|
1056
|
+
* Provider credential encryption uses `safeStorage.encryptString` /
|
|
1057
|
+
* `decryptString` directly inside `providerController` — that's the
|
|
1058
|
+
* only internal caller and stays unchanged.
|
|
1048
1059
|
*/
|
|
1049
1060
|
|
|
1050
1061
|
const { safeStorage } = require$$0$1;
|
|
1051
|
-
const Store$2 = require$$1$1;
|
|
1052
1062
|
const events$6 = events$8;
|
|
1053
1063
|
|
|
1054
|
-
const schema$1 = {
|
|
1055
|
-
appId: {
|
|
1056
|
-
type: "string",
|
|
1057
|
-
},
|
|
1058
|
-
apiKey: {
|
|
1059
|
-
type: "string",
|
|
1060
|
-
},
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
1064
|
const isEncryptionAvailable$1 = (win) => {
|
|
1064
1065
|
const result = safeStorage.isEncryptionAvailable();
|
|
1065
1066
|
win.webContents.send(events$6.SECURE_STORE_ENCRYPTION_CHECK_COMPLETE, result);
|
|
@@ -1075,39 +1076,13 @@ const decryptString = (win, str) => {
|
|
|
1075
1076
|
win.webContents.send("secure-storage-decrypt-string-complete", result);
|
|
1076
1077
|
};
|
|
1077
1078
|
|
|
1078
|
-
const saveData$1 = (key, value) => {
|
|
1079
|
-
try {
|
|
1080
|
-
const store = new Store$2({ schema: schema$1 });
|
|
1081
|
-
store.set(key, value);
|
|
1082
|
-
return getData$1(key);
|
|
1083
|
-
} catch (e) {
|
|
1084
|
-
return { data: null };
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
const getData$1 = (key) => {
|
|
1089
|
-
try {
|
|
1090
|
-
const store = new Store$2({ schema: schema$1 });
|
|
1091
|
-
const value = store.get(key);
|
|
1092
|
-
if (value) {
|
|
1093
|
-
return { [key]: value };
|
|
1094
|
-
} else {
|
|
1095
|
-
return null;
|
|
1096
|
-
}
|
|
1097
|
-
} catch (e) {
|
|
1098
|
-
return null;
|
|
1099
|
-
}
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1102
1079
|
var secureStoreController$1 = {
|
|
1103
1080
|
isEncryptionAvailable: isEncryptionAvailable$1,
|
|
1104
1081
|
encryptString,
|
|
1105
1082
|
decryptString,
|
|
1106
|
-
saveData: saveData$1,
|
|
1107
|
-
getData: getData$1,
|
|
1108
1083
|
};
|
|
1109
1084
|
|
|
1110
|
-
const path$
|
|
1085
|
+
const path$m = require$$1$1;
|
|
1111
1086
|
const {
|
|
1112
1087
|
readFileSync,
|
|
1113
1088
|
writeFileSync: writeFileSync$4,
|
|
@@ -1122,10 +1097,10 @@ const {
|
|
|
1122
1097
|
lstatSync,
|
|
1123
1098
|
} = require$$0$2;
|
|
1124
1099
|
|
|
1125
|
-
function ensureDirectoryExistence$
|
|
1100
|
+
function ensureDirectoryExistence$1(filePath) {
|
|
1126
1101
|
try {
|
|
1127
1102
|
// isDirectory
|
|
1128
|
-
var dirname = path$
|
|
1103
|
+
var dirname = path$m.dirname(filePath);
|
|
1129
1104
|
// check if the directory exists...return true
|
|
1130
1105
|
// if not, we can pass in the dirname as the filepath
|
|
1131
1106
|
// and check each directory recursively.
|
|
@@ -1133,7 +1108,7 @@ function ensureDirectoryExistence$2(filePath) {
|
|
|
1133
1108
|
return true;
|
|
1134
1109
|
}
|
|
1135
1110
|
// recursion...
|
|
1136
|
-
ensureDirectoryExistence$
|
|
1111
|
+
ensureDirectoryExistence$1(dirname);
|
|
1137
1112
|
mkdirSync(dirname);
|
|
1138
1113
|
} catch (e) {
|
|
1139
1114
|
console.log("ensure directory " + e.message);
|
|
@@ -1179,7 +1154,7 @@ function checkDirectory$1(dir) {
|
|
|
1179
1154
|
function getFileContents$8(filepath, defaultReturn = []) {
|
|
1180
1155
|
try {
|
|
1181
1156
|
// lets first make sure all is there...
|
|
1182
|
-
ensureDirectoryExistence$
|
|
1157
|
+
ensureDirectoryExistence$1(filepath);
|
|
1183
1158
|
|
|
1184
1159
|
// and now lets read the file...
|
|
1185
1160
|
let fileContents = JSON.stringify(defaultReturn);
|
|
@@ -1240,7 +1215,7 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
|
|
|
1240
1215
|
|
|
1241
1216
|
for (const file of files) {
|
|
1242
1217
|
if (!excludeFiles.includes(file)) {
|
|
1243
|
-
unlinkSync(path$
|
|
1218
|
+
unlinkSync(path$m.join(directory, file), (err) => {
|
|
1244
1219
|
if (err) throw err;
|
|
1245
1220
|
});
|
|
1246
1221
|
}
|
|
@@ -1250,15 +1225,15 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
|
|
|
1250
1225
|
}
|
|
1251
1226
|
|
|
1252
1227
|
var file = {
|
|
1253
|
-
ensureDirectoryExistence: ensureDirectoryExistence$
|
|
1228
|
+
ensureDirectoryExistence: ensureDirectoryExistence$1,
|
|
1254
1229
|
getFileContents: getFileContents$8,
|
|
1255
1230
|
writeToFile: writeToFile$3,
|
|
1256
1231
|
removeFilesFromDirectory,
|
|
1257
1232
|
checkDirectory: checkDirectory$1,
|
|
1258
1233
|
};
|
|
1259
1234
|
|
|
1260
|
-
const { app: app$
|
|
1261
|
-
const path$
|
|
1235
|
+
const { app: app$e } = require$$0$1;
|
|
1236
|
+
const path$l = require$$1$1;
|
|
1262
1237
|
const { writeFileSync: writeFileSync$3 } = require$$0$2;
|
|
1263
1238
|
const { getFileContents: getFileContents$7 } = file;
|
|
1264
1239
|
|
|
@@ -1305,8 +1280,8 @@ const workspaceController$3 = {
|
|
|
1305
1280
|
saveWorkspaceForApplication: (win, appId, workspaceObject) => {
|
|
1306
1281
|
try {
|
|
1307
1282
|
// filename to the pages file (live pages)
|
|
1308
|
-
const filename = path$
|
|
1309
|
-
app$
|
|
1283
|
+
const filename = path$l.join(
|
|
1284
|
+
app$e.getPath("userData"),
|
|
1310
1285
|
appName$7,
|
|
1311
1286
|
appId,
|
|
1312
1287
|
configFilename$5,
|
|
@@ -1354,8 +1329,8 @@ const workspaceController$3 = {
|
|
|
1354
1329
|
saveMenuItemsForApplication: (win, appId, menuItems) => {
|
|
1355
1330
|
try {
|
|
1356
1331
|
// filename to the workspaces file
|
|
1357
|
-
const filename = path$
|
|
1358
|
-
app$
|
|
1332
|
+
const filename = path$l.join(
|
|
1333
|
+
app$e.getPath("userData"),
|
|
1359
1334
|
appName$7,
|
|
1360
1335
|
appId,
|
|
1361
1336
|
configFilename$5,
|
|
@@ -1403,8 +1378,8 @@ const workspaceController$3 = {
|
|
|
1403
1378
|
*/
|
|
1404
1379
|
deleteWorkspaceForApplication: (win, appId, workspaceId) => {
|
|
1405
1380
|
try {
|
|
1406
|
-
const filename = path$
|
|
1407
|
-
app$
|
|
1381
|
+
const filename = path$l.join(
|
|
1382
|
+
app$e.getPath("userData"),
|
|
1408
1383
|
appName$7,
|
|
1409
1384
|
appId,
|
|
1410
1385
|
configFilename$5,
|
|
@@ -1437,8 +1412,8 @@ const workspaceController$3 = {
|
|
|
1437
1412
|
|
|
1438
1413
|
listWorkspacesForApplication: (win, appId) => {
|
|
1439
1414
|
try {
|
|
1440
|
-
const filename = path$
|
|
1441
|
-
app$
|
|
1415
|
+
const filename = path$l.join(
|
|
1416
|
+
app$e.getPath("userData"),
|
|
1442
1417
|
appName$7,
|
|
1443
1418
|
appId,
|
|
1444
1419
|
configFilename$5,
|
|
@@ -1465,8 +1440,8 @@ const workspaceController$3 = {
|
|
|
1465
1440
|
|
|
1466
1441
|
listMenuItemsForApplication: (win, appId) => {
|
|
1467
1442
|
try {
|
|
1468
|
-
const filename = path$
|
|
1469
|
-
app$
|
|
1443
|
+
const filename = path$l.join(
|
|
1444
|
+
app$e.getPath("userData"),
|
|
1470
1445
|
appName$7,
|
|
1471
1446
|
appId,
|
|
1472
1447
|
configFilename$5,
|
|
@@ -1509,8 +1484,8 @@ const workspaceController$3 = {
|
|
|
1509
1484
|
|
|
1510
1485
|
var workspaceController_1 = workspaceController$3;
|
|
1511
1486
|
|
|
1512
|
-
const { app: app$
|
|
1513
|
-
const path$
|
|
1487
|
+
const { app: app$d } = require$$0$1;
|
|
1488
|
+
const path$k = require$$1$1;
|
|
1514
1489
|
const { writeFileSync: writeFileSync$2 } = require$$0$2;
|
|
1515
1490
|
const { getFileContents: getFileContents$6 } = file;
|
|
1516
1491
|
|
|
@@ -1530,8 +1505,8 @@ const themeController$5 = {
|
|
|
1530
1505
|
saveThemeForApplication: (win, appId, name, obj) => {
|
|
1531
1506
|
try {
|
|
1532
1507
|
// filename to the pages file (live pages)
|
|
1533
|
-
const filename = path$
|
|
1534
|
-
app$
|
|
1508
|
+
const filename = path$k.join(
|
|
1509
|
+
app$d.getPath("userData"),
|
|
1535
1510
|
appName$6,
|
|
1536
1511
|
appId,
|
|
1537
1512
|
configFilename$4,
|
|
@@ -1576,8 +1551,8 @@ const themeController$5 = {
|
|
|
1576
1551
|
*/
|
|
1577
1552
|
listThemesForApplication: (win, appId) => {
|
|
1578
1553
|
try {
|
|
1579
|
-
const filename = path$
|
|
1580
|
-
app$
|
|
1554
|
+
const filename = path$k.join(
|
|
1555
|
+
app$d.getPath("userData"),
|
|
1581
1556
|
appName$6,
|
|
1582
1557
|
appId,
|
|
1583
1558
|
configFilename$4,
|
|
@@ -1618,8 +1593,8 @@ const themeController$5 = {
|
|
|
1618
1593
|
*/
|
|
1619
1594
|
deleteThemeForApplication: (win, appId, themeKey) => {
|
|
1620
1595
|
try {
|
|
1621
|
-
const filename = path$
|
|
1622
|
-
app$
|
|
1596
|
+
const filename = path$k.join(
|
|
1597
|
+
app$d.getPath("userData"),
|
|
1623
1598
|
appName$6,
|
|
1624
1599
|
appId,
|
|
1625
1600
|
configFilename$4,
|
|
@@ -1695,9 +1670,9 @@ var themeController_1 = themeController$5;
|
|
|
1695
1670
|
* `/data/`.
|
|
1696
1671
|
*/
|
|
1697
1672
|
|
|
1698
|
-
const path$
|
|
1699
|
-
const fs$
|
|
1700
|
-
const { app: app$
|
|
1673
|
+
const path$j = require$$1$1;
|
|
1674
|
+
const fs$e = require$$0$2;
|
|
1675
|
+
const { app: app$c } = require$$0$1;
|
|
1701
1676
|
|
|
1702
1677
|
const APP_NAME = "Dashboard";
|
|
1703
1678
|
|
|
@@ -1706,10 +1681,10 @@ const APP_NAME = "Dashboard";
|
|
|
1706
1681
|
* @returns {string[]} ordered allowed roots for that category
|
|
1707
1682
|
*/
|
|
1708
1683
|
function getAllowedRoots$2(category) {
|
|
1709
|
-
const userData = app$
|
|
1684
|
+
const userData = app$c.getPath("userData");
|
|
1710
1685
|
switch (category) {
|
|
1711
1686
|
case "data": {
|
|
1712
|
-
const def = path$
|
|
1687
|
+
const def = path$j.join(userData, APP_NAME, "data");
|
|
1713
1688
|
// The user can configure a custom data directory in
|
|
1714
1689
|
// Settings → General → Data Directory. If set, that
|
|
1715
1690
|
// location is ALSO an allowed root. We don't replace the
|
|
@@ -1719,13 +1694,13 @@ function getAllowedRoots$2(category) {
|
|
|
1719
1694
|
return override ? [def, override] : [def];
|
|
1720
1695
|
}
|
|
1721
1696
|
case "themes":
|
|
1722
|
-
return [path$
|
|
1697
|
+
return [path$j.join(userData, APP_NAME, "themes")];
|
|
1723
1698
|
case "widgets":
|
|
1724
|
-
return [path$
|
|
1699
|
+
return [path$j.join(userData, "widgets")];
|
|
1725
1700
|
case "plugins":
|
|
1726
|
-
return [path$
|
|
1701
|
+
return [path$j.join(userData, "plugins")];
|
|
1727
1702
|
case "downloads":
|
|
1728
|
-
return [app$
|
|
1703
|
+
return [app$c.getPath("downloads")];
|
|
1729
1704
|
default:
|
|
1730
1705
|
throw new Error("safePath: unknown allowed-roots category: " + category);
|
|
1731
1706
|
}
|
|
@@ -1740,13 +1715,13 @@ function getAllowedRoots$2(category) {
|
|
|
1740
1715
|
*/
|
|
1741
1716
|
function readDataDirectoryFromSettings() {
|
|
1742
1717
|
try {
|
|
1743
|
-
const settingsPath = path$
|
|
1744
|
-
app$
|
|
1718
|
+
const settingsPath = path$j.join(
|
|
1719
|
+
app$c.getPath("userData"),
|
|
1745
1720
|
APP_NAME,
|
|
1746
1721
|
"settings.json",
|
|
1747
1722
|
);
|
|
1748
|
-
if (!fs$
|
|
1749
|
-
const raw = fs$
|
|
1723
|
+
if (!fs$e.existsSync(settingsPath)) return undefined;
|
|
1724
|
+
const raw = fs$e.readFileSync(settingsPath, "utf8");
|
|
1750
1725
|
const settings = JSON.parse(raw);
|
|
1751
1726
|
const dir = settings && settings.dataDirectory;
|
|
1752
1727
|
if (typeof dir === "string" && dir) return dir;
|
|
@@ -1772,18 +1747,18 @@ function safePath$3(requested, allowedRoots) {
|
|
|
1772
1747
|
throw new Error("safePath: allowedRoots must be a non-empty array");
|
|
1773
1748
|
}
|
|
1774
1749
|
|
|
1775
|
-
const resolved = path$
|
|
1750
|
+
const resolved = path$j.resolve(requested);
|
|
1776
1751
|
|
|
1777
1752
|
// Real-path through symlinks. If the file doesn't exist yet (a
|
|
1778
1753
|
// create-new operation), real-path the parent so a symlink in the
|
|
1779
1754
|
// parent chain can't trick us.
|
|
1780
1755
|
let real = resolved;
|
|
1781
1756
|
try {
|
|
1782
|
-
real = fs$
|
|
1757
|
+
real = fs$e.realpathSync(resolved);
|
|
1783
1758
|
} catch (_e) {
|
|
1784
1759
|
try {
|
|
1785
|
-
const parent = fs$
|
|
1786
|
-
real = path$
|
|
1760
|
+
const parent = fs$e.realpathSync(path$j.dirname(resolved));
|
|
1761
|
+
real = path$j.join(parent, path$j.basename(resolved));
|
|
1787
1762
|
} catch (_e2) {
|
|
1788
1763
|
// Parent doesn't exist either. Use the resolved-but-not-
|
|
1789
1764
|
// real path; the caller's mkdirSync will happen inside the
|
|
@@ -1795,14 +1770,14 @@ function safePath$3(requested, allowedRoots) {
|
|
|
1795
1770
|
for (const root of allowedRoots) {
|
|
1796
1771
|
let realRoot = root;
|
|
1797
1772
|
try {
|
|
1798
|
-
if (fs$
|
|
1773
|
+
if (fs$e.existsSync(root)) realRoot = fs$e.realpathSync(root);
|
|
1799
1774
|
} catch (_e) {
|
|
1800
1775
|
// root doesn't exist or isn't reachable — keep as-is for
|
|
1801
1776
|
// the comparison below
|
|
1802
1777
|
}
|
|
1803
1778
|
// Exact match OR strictly-inside (with separator to prevent
|
|
1804
1779
|
// /data-evil/ matching /data/).
|
|
1805
|
-
if (real === realRoot || real.startsWith(realRoot + path$
|
|
1780
|
+
if (real === realRoot || real.startsWith(realRoot + path$j.sep)) {
|
|
1806
1781
|
return real;
|
|
1807
1782
|
}
|
|
1808
1783
|
}
|
|
@@ -1823,6 +1798,795 @@ var safePath_1 = {
|
|
|
1823
1798
|
getAllowedRoots: getAllowedRoots$2,
|
|
1824
1799
|
};
|
|
1825
1800
|
|
|
1801
|
+
/**
|
|
1802
|
+
* grantedPermissions.js
|
|
1803
|
+
*
|
|
1804
|
+
* Stores the user's actual MCP permission grants per widget. This is the
|
|
1805
|
+
* Slice-2 enforcement source of truth — separate from the widget's declared
|
|
1806
|
+
* `dash.permissions.mcp` block (which is just a request).
|
|
1807
|
+
*
|
|
1808
|
+
* The runtime gate (permissionGate.gateToolCall) reads from here only.
|
|
1809
|
+
* A widget with a declared manifest but no grant entry has no access:
|
|
1810
|
+
* fail-closed. The user grants permissions at install time (consent modal)
|
|
1811
|
+
* or later in Settings → Privacy & Security.
|
|
1812
|
+
*
|
|
1813
|
+
* Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
|
|
1814
|
+
*
|
|
1815
|
+
* Shape on disk:
|
|
1816
|
+
* {
|
|
1817
|
+
* "@trops/notes-summarizer": {
|
|
1818
|
+
* "servers": {
|
|
1819
|
+
* "filesystem": {
|
|
1820
|
+
* "tools": ["read_file"],
|
|
1821
|
+
* "readPaths": ["/Users/jane/Documents/notes"],
|
|
1822
|
+
* "writePaths": []
|
|
1823
|
+
* }
|
|
1824
|
+
* }
|
|
1825
|
+
* }
|
|
1826
|
+
* }
|
|
1827
|
+
*
|
|
1828
|
+
* Note: paths are stored as-is (already tilde-expanded by the manifest
|
|
1829
|
+
* parser before grants are written). Tests can re-expand via
|
|
1830
|
+
* widgetPermissions.expandHome if they store ~ literals.
|
|
1831
|
+
*
|
|
1832
|
+
* Public API:
|
|
1833
|
+
* getGrant(widgetId) → grant | null
|
|
1834
|
+
* setGrant(widgetId, perms) → boolean
|
|
1835
|
+
* revokeGrant(widgetId) → boolean
|
|
1836
|
+
* revokeServer(widgetId, serverName) → boolean
|
|
1837
|
+
* listAllGrants() → [{ widgetId, granted }]
|
|
1838
|
+
* clearCache() → void // test-only
|
|
1839
|
+
*/
|
|
1840
|
+
|
|
1841
|
+
const fs$d = require$$0$2;
|
|
1842
|
+
const path$i = require$$1$1;
|
|
1843
|
+
const { app: app$b } = require$$0$1;
|
|
1844
|
+
|
|
1845
|
+
const FILE_NAME = "widgetMcpGrants.json";
|
|
1846
|
+
|
|
1847
|
+
// In-process cache of the entire grants file. Lazily loaded; invalidated
|
|
1848
|
+
// on every write.
|
|
1849
|
+
let _cache$1 = null;
|
|
1850
|
+
|
|
1851
|
+
function grantsFilePath() {
|
|
1852
|
+
return path$i.join(app$b.getPath("userData"), FILE_NAME);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function loadFromDisk() {
|
|
1856
|
+
const p = grantsFilePath();
|
|
1857
|
+
if (!fs$d.existsSync(p)) return {};
|
|
1858
|
+
try {
|
|
1859
|
+
const raw = fs$d.readFileSync(p, "utf8");
|
|
1860
|
+
const parsed = JSON.parse(raw);
|
|
1861
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
1862
|
+
return parsed;
|
|
1863
|
+
} catch (e) {
|
|
1864
|
+
console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
|
|
1865
|
+
return {};
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function ensureCache() {
|
|
1870
|
+
if (_cache$1 === null) _cache$1 = loadFromDisk();
|
|
1871
|
+
return _cache$1;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function writeToDisk(data) {
|
|
1875
|
+
const p = grantsFilePath();
|
|
1876
|
+
const tmp = p + ".tmp";
|
|
1877
|
+
// Ensure parent dir exists (userData should already, but be defensive
|
|
1878
|
+
// for first-launch / freshly-cleared profile cases).
|
|
1879
|
+
fs$d.mkdirSync(path$i.dirname(p), { recursive: true });
|
|
1880
|
+
fs$d.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
|
|
1881
|
+
fs$d.renameSync(tmp, p);
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// Recognized origins for a persisted grant.
|
|
1885
|
+
// "declared" — user approved against the developer's declared
|
|
1886
|
+
// dash.permissions.mcp block at install time.
|
|
1887
|
+
// "discovered" — install-time scanner produced a synthetic manifest
|
|
1888
|
+
// the user approved.
|
|
1889
|
+
// "manual" — user typed entries themselves in
|
|
1890
|
+
// Settings → Privacy & Security with no manifest backing.
|
|
1891
|
+
// "live" — user approved a just-in-time consent prompt at
|
|
1892
|
+
// runtime when a tool call hit the gate without a
|
|
1893
|
+
// matching grant.
|
|
1894
|
+
// Other values are dropped on persist (legacy grants stay null).
|
|
1895
|
+
const ALLOWED_GRANT_ORIGINS = new Set([
|
|
1896
|
+
"declared",
|
|
1897
|
+
"discovered",
|
|
1898
|
+
"manual",
|
|
1899
|
+
"live",
|
|
1900
|
+
]);
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* Sanitize a perms object before persisting. Drops unknown keys, coerces
|
|
1904
|
+
* arrays of strings, and silently ignores malformed servers. Mirrors the
|
|
1905
|
+
* shape produced by parseManifestPermissions so the gate reads either
|
|
1906
|
+
* declared or granted with the same code path.
|
|
1907
|
+
*
|
|
1908
|
+
* Optional `grantOrigin` field is preserved when it's one of the
|
|
1909
|
+
* recognized values; bogus values are dropped.
|
|
1910
|
+
*
|
|
1911
|
+
* Phase 2 (JIT consent for non-MCP domains): also accepts a top-level
|
|
1912
|
+
* `domains` block. Each known domain has its own shape — `domains.fs`
|
|
1913
|
+
* has `readPaths`/`writePaths`, future domains will have their own.
|
|
1914
|
+
* The `servers` block (MCP) and `domains` block coexist on the same
|
|
1915
|
+
* grant; either or both may be present.
|
|
1916
|
+
*
|
|
1917
|
+
* Either `servers` (MCP) or any `domains.*` block is enough to make the
|
|
1918
|
+
* grant non-empty. If neither is present, the grant is rejected as
|
|
1919
|
+
* malformed.
|
|
1920
|
+
*/
|
|
1921
|
+
function sanitizePerms(perms) {
|
|
1922
|
+
if (!perms || typeof perms !== "object") return null;
|
|
1923
|
+
const rawServers =
|
|
1924
|
+
perms.servers && typeof perms.servers === "object" ? perms.servers : null;
|
|
1925
|
+
const rawDomains =
|
|
1926
|
+
perms.domains && typeof perms.domains === "object" ? perms.domains : null;
|
|
1927
|
+
if (!rawServers && !rawDomains) return null;
|
|
1928
|
+
|
|
1929
|
+
const out = {};
|
|
1930
|
+
|
|
1931
|
+
if (rawServers) {
|
|
1932
|
+
const servers = {};
|
|
1933
|
+
for (const [name, raw] of Object.entries(rawServers)) {
|
|
1934
|
+
if (!raw || typeof raw !== "object") continue;
|
|
1935
|
+
servers[name] = {
|
|
1936
|
+
tools: Array.isArray(raw.tools)
|
|
1937
|
+
? raw.tools.filter((t) => typeof t === "string")
|
|
1938
|
+
: [],
|
|
1939
|
+
readPaths: Array.isArray(raw.readPaths)
|
|
1940
|
+
? raw.readPaths.filter((p) => typeof p === "string")
|
|
1941
|
+
: [],
|
|
1942
|
+
writePaths: Array.isArray(raw.writePaths)
|
|
1943
|
+
? raw.writePaths.filter((p) => typeof p === "string")
|
|
1944
|
+
: [],
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
out.servers = servers;
|
|
1948
|
+
} else {
|
|
1949
|
+
// Always emit `servers` so consumers don't have to null-check it.
|
|
1950
|
+
out.servers = {};
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
if (rawDomains) {
|
|
1954
|
+
const domains = {};
|
|
1955
|
+
for (const [name, raw] of Object.entries(rawDomains)) {
|
|
1956
|
+
if (!raw || typeof raw !== "object") continue;
|
|
1957
|
+
if (name === "fs") {
|
|
1958
|
+
domains.fs = {
|
|
1959
|
+
readPaths: Array.isArray(raw.readPaths)
|
|
1960
|
+
? raw.readPaths.filter((p) => typeof p === "string")
|
|
1961
|
+
: [],
|
|
1962
|
+
writePaths: Array.isArray(raw.writePaths)
|
|
1963
|
+
? raw.writePaths.filter((p) => typeof p === "string")
|
|
1964
|
+
: [],
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
// Future domains plug in here. Unknown domain names are dropped.
|
|
1968
|
+
}
|
|
1969
|
+
if (Object.keys(domains).length > 0) {
|
|
1970
|
+
out.domains = domains;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
if (
|
|
1975
|
+
typeof perms.grantOrigin === "string" &&
|
|
1976
|
+
ALLOWED_GRANT_ORIGINS.has(perms.grantOrigin)
|
|
1977
|
+
) {
|
|
1978
|
+
out.grantOrigin = perms.grantOrigin;
|
|
1979
|
+
}
|
|
1980
|
+
return out;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
function getGrant$3(widgetId) {
|
|
1984
|
+
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
1985
|
+
const all = ensureCache();
|
|
1986
|
+
return all[widgetId] || null;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
function setGrant$3(widgetId, perms) {
|
|
1990
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
1991
|
+
const sanitized = sanitizePerms(perms);
|
|
1992
|
+
if (!sanitized) return false;
|
|
1993
|
+
const all = ensureCache();
|
|
1994
|
+
all[widgetId] = sanitized;
|
|
1995
|
+
try {
|
|
1996
|
+
writeToDisk(all);
|
|
1997
|
+
return true;
|
|
1998
|
+
} catch (e) {
|
|
1999
|
+
console.warn(
|
|
2000
|
+
"[grantedPermissions] failed to write grant for " +
|
|
2001
|
+
widgetId +
|
|
2002
|
+
": " +
|
|
2003
|
+
e.message,
|
|
2004
|
+
);
|
|
2005
|
+
// Roll back the cache entry so memory matches disk.
|
|
2006
|
+
_cache$1 = loadFromDisk();
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function revokeGrant$1(widgetId) {
|
|
2012
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
2013
|
+
const all = ensureCache();
|
|
2014
|
+
if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
|
|
2015
|
+
delete all[widgetId];
|
|
2016
|
+
try {
|
|
2017
|
+
writeToDisk(all);
|
|
2018
|
+
return true;
|
|
2019
|
+
} catch (e) {
|
|
2020
|
+
console.warn(
|
|
2021
|
+
"[grantedPermissions] failed to revoke grant for " +
|
|
2022
|
+
widgetId +
|
|
2023
|
+
": " +
|
|
2024
|
+
e.message,
|
|
2025
|
+
);
|
|
2026
|
+
_cache$1 = loadFromDisk();
|
|
2027
|
+
return false;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function revokeServer$1(widgetId, serverName) {
|
|
2032
|
+
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
2033
|
+
if (typeof serverName !== "string" || !serverName) return false;
|
|
2034
|
+
const all = ensureCache();
|
|
2035
|
+
const widgetEntry = all[widgetId];
|
|
2036
|
+
if (!widgetEntry || !widgetEntry.servers) return false;
|
|
2037
|
+
if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
|
|
2038
|
+
return false;
|
|
2039
|
+
delete widgetEntry.servers[serverName];
|
|
2040
|
+
try {
|
|
2041
|
+
writeToDisk(all);
|
|
2042
|
+
return true;
|
|
2043
|
+
} catch (e) {
|
|
2044
|
+
console.warn(
|
|
2045
|
+
"[grantedPermissions] failed to revoke server " +
|
|
2046
|
+
serverName +
|
|
2047
|
+
" for " +
|
|
2048
|
+
widgetId +
|
|
2049
|
+
": " +
|
|
2050
|
+
e.message,
|
|
2051
|
+
);
|
|
2052
|
+
_cache$1 = loadFromDisk();
|
|
2053
|
+
return false;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function listAllGrants$1() {
|
|
2058
|
+
const all = ensureCache();
|
|
2059
|
+
return Object.entries(all).map(([widgetId, granted]) => ({
|
|
2060
|
+
widgetId,
|
|
2061
|
+
granted,
|
|
2062
|
+
}));
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
function clearCache$1() {
|
|
2066
|
+
_cache$1 = null;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
var grantedPermissions = {
|
|
2070
|
+
getGrant: getGrant$3,
|
|
2071
|
+
setGrant: setGrant$3,
|
|
2072
|
+
revokeGrant: revokeGrant$1,
|
|
2073
|
+
revokeServer: revokeServer$1,
|
|
2074
|
+
listAllGrants: listAllGrants$1,
|
|
2075
|
+
clearCache: clearCache$1,
|
|
2076
|
+
ALLOWED_GRANT_ORIGINS,
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* jitConsent.js
|
|
2081
|
+
*
|
|
2082
|
+
* Just-in-time permission consent for widget→backend calls.
|
|
2083
|
+
*
|
|
2084
|
+
* When a widget hits a gate without an existing grant for the requested
|
|
2085
|
+
* (domain, action, args), the gate calls `requestApproval` which:
|
|
2086
|
+
* 1. Synchronously emits `widget:permission-required` to all
|
|
2087
|
+
* BrowserWindows with a unique requestId.
|
|
2088
|
+
* 2. Returns a Promise that resolves on user response or rejects on
|
|
2089
|
+
* timeout.
|
|
2090
|
+
* 3. Coalesces requests with the same coalescing key so a widget
|
|
2091
|
+
* bursting identical calls produces one prompt, not many.
|
|
2092
|
+
*
|
|
2093
|
+
* The renderer's JitConsentModal subscribes to the event, presents the
|
|
2094
|
+
* user with granularity options (this once / this tool / this tool +
|
|
2095
|
+
* parent dir), and replies via `widget:permission-response` with
|
|
2096
|
+
* `{ requestId, decision }`. main.js wires the IPC handler back to
|
|
2097
|
+
* `_handleResponse`.
|
|
2098
|
+
*
|
|
2099
|
+
* The module is intentionally domain-agnostic in shape — the request
|
|
2100
|
+
* payload carries `domain` so future plug-ins (fs, algolia, llm) reuse
|
|
2101
|
+
* the same machinery. Phase 1 only emits with `domain: "mcp"`.
|
|
2102
|
+
*
|
|
2103
|
+
* Public surface:
|
|
2104
|
+
* requestApproval(req, opts) → Promise<{ approve, scope?, ... }>
|
|
2105
|
+
* _handleResponse({ requestId, decision }) → void (called from main.js IPC)
|
|
2106
|
+
* _resetForTest() → void (test-only)
|
|
2107
|
+
*/
|
|
2108
|
+
|
|
2109
|
+
const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
|
|
2110
|
+
|
|
2111
|
+
const REQUEST_CHANNEL = "widget:permission-required";
|
|
2112
|
+
const RESPONSE_CHANNEL = "widget:permission-response";
|
|
2113
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
2114
|
+
|
|
2115
|
+
// requestId → { resolve, reject, timeout, coalesceKey, joinedResolvers }
|
|
2116
|
+
const _pending = new Map();
|
|
2117
|
+
// coalesceKey → requestId (so duplicate requests join the live one)
|
|
2118
|
+
const _coalesce = new Map();
|
|
2119
|
+
let _idCounter = 0;
|
|
2120
|
+
|
|
2121
|
+
function nextRequestId() {
|
|
2122
|
+
_idCounter += 1;
|
|
2123
|
+
return `jit-${Date.now()}-${_idCounter}`;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
/**
|
|
2127
|
+
* Build a coalescing key from the request. Two requests share the same
|
|
2128
|
+
* key iff they're "the same prompt" — same widget, same domain+action,
|
|
2129
|
+
* same target server/tool. Args beyond that (e.g. exact path) DON'T
|
|
2130
|
+
* differentiate; if the user is being asked about read_file already,
|
|
2131
|
+
* approving handles all current paths.
|
|
2132
|
+
*/
|
|
2133
|
+
function coalesceKeyOf(req) {
|
|
2134
|
+
if (req.domain === "mcp") {
|
|
2135
|
+
const innerArgs = req.args || {};
|
|
2136
|
+
return [
|
|
2137
|
+
req.widgetId,
|
|
2138
|
+
"mcp",
|
|
2139
|
+
innerArgs.serverName || "",
|
|
2140
|
+
innerArgs.toolName || "",
|
|
2141
|
+
].join("::");
|
|
2142
|
+
}
|
|
2143
|
+
// Default: domain + action + serialized top-level args
|
|
2144
|
+
return [
|
|
2145
|
+
req.widgetId,
|
|
2146
|
+
req.domain,
|
|
2147
|
+
req.action,
|
|
2148
|
+
JSON.stringify(req.args || {}),
|
|
2149
|
+
].join("::");
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
function emitEvent(payload) {
|
|
2153
|
+
let wins = [];
|
|
2154
|
+
try {
|
|
2155
|
+
wins = BrowserWindow$2.getAllWindows() || [];
|
|
2156
|
+
} catch {
|
|
2157
|
+
wins = [];
|
|
2158
|
+
}
|
|
2159
|
+
for (const w of wins) {
|
|
2160
|
+
try {
|
|
2161
|
+
w?.webContents?.send?.(REQUEST_CHANNEL, payload);
|
|
2162
|
+
} catch {
|
|
2163
|
+
// best-effort broadcast
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
function validateRequest(req) {
|
|
2169
|
+
if (!req || typeof req !== "object") return "invalid request: not an object";
|
|
2170
|
+
if (typeof req.widgetId !== "string" || !req.widgetId)
|
|
2171
|
+
return "invalid request: widgetId required";
|
|
2172
|
+
if (typeof req.domain !== "string" || !req.domain)
|
|
2173
|
+
return "invalid request: domain required";
|
|
2174
|
+
if (typeof req.action !== "string" || !req.action)
|
|
2175
|
+
return "invalid request: action required";
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
/**
|
|
2180
|
+
* Request user approval for an out-of-grant call. Returns a promise
|
|
2181
|
+
* that resolves with the user's decision or rejects on timeout / bad
|
|
2182
|
+
* input.
|
|
2183
|
+
*
|
|
2184
|
+
* decision shape (resolved value):
|
|
2185
|
+
* { approve: true, scope: "once" | "tool" | "parent" | "custom", ...extras }
|
|
2186
|
+
* { approve: false, reason?: string }
|
|
2187
|
+
*
|
|
2188
|
+
* `scope` informs the caller how to write the resulting grant.
|
|
2189
|
+
*/
|
|
2190
|
+
function requestApproval$2(req, opts = {}) {
|
|
2191
|
+
const validation = validateRequest(req);
|
|
2192
|
+
if (validation) {
|
|
2193
|
+
return Promise.reject(new Error(validation));
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
const timeoutMs = Number.isFinite(opts.timeoutMs)
|
|
2197
|
+
? opts.timeoutMs
|
|
2198
|
+
: DEFAULT_TIMEOUT_MS;
|
|
2199
|
+
|
|
2200
|
+
// If a prompt for the same coalesce key is already pending, join it.
|
|
2201
|
+
const key = coalesceKeyOf(req);
|
|
2202
|
+
if (_coalesce.has(key)) {
|
|
2203
|
+
const existingId = _coalesce.get(key);
|
|
2204
|
+
const existing = _pending.get(existingId);
|
|
2205
|
+
if (existing) {
|
|
2206
|
+
return new Promise((resolve, reject) => {
|
|
2207
|
+
existing.joinedResolvers.push({ resolve, reject });
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
// Stale coalesce entry; drop and fall through to a fresh request.
|
|
2211
|
+
_coalesce.delete(key);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
return new Promise((resolve, reject) => {
|
|
2215
|
+
const requestId = nextRequestId();
|
|
2216
|
+
const timeout = setTimeout(() => {
|
|
2217
|
+
const entry = _pending.get(requestId);
|
|
2218
|
+
if (!entry) return;
|
|
2219
|
+
_pending.delete(requestId);
|
|
2220
|
+
_coalesce.delete(entry.coalesceKey);
|
|
2221
|
+
const err = new Error(
|
|
2222
|
+
`JIT consent timed out for ${req.widgetId} (${req.domain}/${req.action}) after ${timeoutMs}ms`,
|
|
2223
|
+
);
|
|
2224
|
+
reject(err);
|
|
2225
|
+
for (const j of entry.joinedResolvers) j.reject(err);
|
|
2226
|
+
}, timeoutMs);
|
|
2227
|
+
|
|
2228
|
+
_pending.set(requestId, {
|
|
2229
|
+
resolve,
|
|
2230
|
+
reject,
|
|
2231
|
+
timeout,
|
|
2232
|
+
coalesceKey: key,
|
|
2233
|
+
joinedResolvers: [],
|
|
2234
|
+
});
|
|
2235
|
+
_coalesce.set(key, requestId);
|
|
2236
|
+
|
|
2237
|
+
emitEvent({
|
|
2238
|
+
requestId,
|
|
2239
|
+
widgetId: req.widgetId,
|
|
2240
|
+
domain: req.domain,
|
|
2241
|
+
action: req.action,
|
|
2242
|
+
args: req.args || {},
|
|
2243
|
+
});
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
function _handleResponse({ requestId, decision } = {}) {
|
|
2248
|
+
if (!requestId || typeof requestId !== "string") return;
|
|
2249
|
+
const entry = _pending.get(requestId);
|
|
2250
|
+
if (!entry) return; // unknown request — drop silently
|
|
2251
|
+
clearTimeout(entry.timeout);
|
|
2252
|
+
_pending.delete(requestId);
|
|
2253
|
+
_coalesce.delete(entry.coalesceKey);
|
|
2254
|
+
const safe =
|
|
2255
|
+
decision && typeof decision === "object" ? decision : { approve: false };
|
|
2256
|
+
entry.resolve(safe);
|
|
2257
|
+
for (const j of entry.joinedResolvers) j.resolve(safe);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function _resetForTest() {
|
|
2261
|
+
for (const entry of _pending.values()) clearTimeout(entry.timeout);
|
|
2262
|
+
_pending.clear();
|
|
2263
|
+
_coalesce.clear();
|
|
2264
|
+
_idCounter = 0;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
let _handlersRegistered = false;
|
|
2268
|
+
/**
|
|
2269
|
+
* Wire the renderer→main response IPC. Idempotent.
|
|
2270
|
+
* Call once from main.js alongside other ipcMain setup.
|
|
2271
|
+
*/
|
|
2272
|
+
function setupJitConsentHandlers() {
|
|
2273
|
+
if (_handlersRegistered) return;
|
|
2274
|
+
if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
|
|
2275
|
+
ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
|
|
2276
|
+
_handleResponse(payload);
|
|
2277
|
+
});
|
|
2278
|
+
_handlersRegistered = true;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
var jitConsent$1 = {
|
|
2282
|
+
requestApproval: requestApproval$2,
|
|
2283
|
+
setupJitConsentHandlers,
|
|
2284
|
+
_handleResponse,
|
|
2285
|
+
_resetForTest,
|
|
2286
|
+
REQUEST_CHANNEL,
|
|
2287
|
+
RESPONSE_CHANNEL,
|
|
2288
|
+
DEFAULT_TIMEOUT_MS,
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
/**
|
|
2292
|
+
* fsGate.js
|
|
2293
|
+
*
|
|
2294
|
+
* Per-widget gate for `mainApi.data.*` IPC handlers (Phase 2 of JIT
|
|
2295
|
+
* consent). Same shape as `electron/mcp/permissionGate.js` but for the
|
|
2296
|
+
* filesystem domain — saveToFile/readFromFile in dataController.
|
|
2297
|
+
*
|
|
2298
|
+
* The gate evaluates against the widget's persisted grant under
|
|
2299
|
+
* `grant.domains.fs.{readPaths,writePaths}`. The existing `safePath()`
|
|
2300
|
+
* containment in dataController is unchanged — that constrains paths
|
|
2301
|
+
* to the userData/Dashboard/data dir; this gate adds per-widget
|
|
2302
|
+
* identity scoping on top, so two widgets can't read each other's
|
|
2303
|
+
* files even though both are inside the data dir.
|
|
2304
|
+
*
|
|
2305
|
+
* Action → read/write classification by name. Read tools may match
|
|
2306
|
+
* either readPaths or writePaths (write access implies read); write
|
|
2307
|
+
* tools must match writePaths only.
|
|
2308
|
+
*
|
|
2309
|
+
* Filename matching:
|
|
2310
|
+
* - Exact match
|
|
2311
|
+
* - "*" wildcard in the grant matches any filename (escape hatch
|
|
2312
|
+
* for users who want broad widget access; surfaced in the JIT
|
|
2313
|
+
* modal as "no path scope — risky")
|
|
2314
|
+
*
|
|
2315
|
+
* Path-traversal protection lives in safePath; the gate doesn't
|
|
2316
|
+
* re-check it. The gate is purely an identity+filename allowlist on
|
|
2317
|
+
* top of safePath's containment.
|
|
2318
|
+
*
|
|
2319
|
+
* JIT escalation: when the runtime calls `gateFsCallWithJit` and the
|
|
2320
|
+
* gate denies for "no fs permissions granted", a permission-required
|
|
2321
|
+
* IPC fires and the user's response is merged into the persisted
|
|
2322
|
+
* grant. Other denial reasons (filename not in allowlist, write to a
|
|
2323
|
+
* read-only entry) stay synchronous.
|
|
2324
|
+
*/
|
|
2325
|
+
|
|
2326
|
+
const { getGrant: getGrant$2, setGrant: setGrant$2 } = grantedPermissions;
|
|
2327
|
+
const { requestApproval: requestApproval$1 } = jitConsent$1;
|
|
2328
|
+
|
|
2329
|
+
// Action names treated as writes. Anything not in this set is a read.
|
|
2330
|
+
// Conservative — when in doubt, classify as a read so write-protected
|
|
2331
|
+
// grants don't accidentally allow writes.
|
|
2332
|
+
const WRITE_ACTIONS = new Set([
|
|
2333
|
+
"saveToFile",
|
|
2334
|
+
"saveData", // future-proof: the renderer-facing API name
|
|
2335
|
+
"convertJsonToCsvFile",
|
|
2336
|
+
"parseXMLStream",
|
|
2337
|
+
"parseCSVStream",
|
|
2338
|
+
"readDataFromURL", // writes to toFilepath despite the name
|
|
2339
|
+
"transformFile",
|
|
2340
|
+
]);
|
|
2341
|
+
|
|
2342
|
+
function isFsWriteAction(action) {
|
|
2343
|
+
return WRITE_ACTIONS.has(action);
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
function _isNoGrantDenial$1(reason) {
|
|
2347
|
+
return (
|
|
2348
|
+
typeof reason === "string" && /no fs permissions granted/i.test(reason)
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
function _filenameMatches(filename, allowedList) {
|
|
2353
|
+
if (!Array.isArray(allowedList) || allowedList.length === 0) return false;
|
|
2354
|
+
if (allowedList.includes("*")) return true;
|
|
2355
|
+
return allowedList.includes(filename);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
/**
|
|
2359
|
+
* Synchronous gate evaluation.
|
|
2360
|
+
* @returns {{ allow: true } | { allow: false, reason: string }}
|
|
2361
|
+
*/
|
|
2362
|
+
function gateFsCall$1({ widgetId, action, args }) {
|
|
2363
|
+
if (!widgetId) {
|
|
2364
|
+
return {
|
|
2365
|
+
allow: false,
|
|
2366
|
+
reason: "no widgetId supplied; cannot determine fs permissions",
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
const filename = args && typeof args === "object" ? args.filename : null;
|
|
2371
|
+
if (typeof filename !== "string" || !filename) {
|
|
2372
|
+
return {
|
|
2373
|
+
allow: false,
|
|
2374
|
+
reason:
|
|
2375
|
+
"fs gate: action '" +
|
|
2376
|
+
action +
|
|
2377
|
+
"' requires args.filename (got: " +
|
|
2378
|
+
JSON.stringify(filename) +
|
|
2379
|
+
")",
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
const grant = getGrant$2(widgetId);
|
|
2384
|
+
const fsPerms = grant && grant.domains && grant.domains.fs;
|
|
2385
|
+
if (!fsPerms) {
|
|
2386
|
+
return {
|
|
2387
|
+
allow: false,
|
|
2388
|
+
reason:
|
|
2389
|
+
"widget '" +
|
|
2390
|
+
widgetId +
|
|
2391
|
+
"' has no fs permissions granted; user must approve at runtime or in Settings → Privacy & Security",
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
const isWrite = isFsWriteAction(action);
|
|
2396
|
+
|
|
2397
|
+
if (isWrite) {
|
|
2398
|
+
if (!Array.isArray(fsPerms.writePaths) || fsPerms.writePaths.length === 0) {
|
|
2399
|
+
return {
|
|
2400
|
+
allow: false,
|
|
2401
|
+
reason:
|
|
2402
|
+
"fs gate: widget '" +
|
|
2403
|
+
widgetId +
|
|
2404
|
+
"' has no writePaths granted for action '" +
|
|
2405
|
+
action +
|
|
2406
|
+
"'",
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
if (!_filenameMatches(filename, fsPerms.writePaths)) {
|
|
2410
|
+
return {
|
|
2411
|
+
allow: false,
|
|
2412
|
+
reason:
|
|
2413
|
+
"fs gate: filename '" +
|
|
2414
|
+
filename +
|
|
2415
|
+
"' rejected — not in allowed writePaths for widget '" +
|
|
2416
|
+
widgetId +
|
|
2417
|
+
"'",
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
return { allow: true };
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// Read action — may use readPaths OR writePaths (write implies read)
|
|
2424
|
+
if (
|
|
2425
|
+
_filenameMatches(filename, fsPerms.readPaths) ||
|
|
2426
|
+
_filenameMatches(filename, fsPerms.writePaths)
|
|
2427
|
+
) {
|
|
2428
|
+
return { allow: true };
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
allow: false,
|
|
2432
|
+
reason:
|
|
2433
|
+
"fs gate: filename '" +
|
|
2434
|
+
filename +
|
|
2435
|
+
"' rejected — not in allowed readPaths or writePaths for widget '" +
|
|
2436
|
+
widgetId +
|
|
2437
|
+
"'",
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
/**
|
|
2442
|
+
* Merge an approved JIT decision's grant into the widget's existing
|
|
2443
|
+
* grant under domains.fs. Same shape as permissionGate._mergeGrant
|
|
2444
|
+
* but scoped to fs.
|
|
2445
|
+
*/
|
|
2446
|
+
function _mergeFsGrant(current, addition) {
|
|
2447
|
+
const out = {
|
|
2448
|
+
grantOrigin: addition.grantOrigin || current?.grantOrigin || null,
|
|
2449
|
+
servers: { ...(current?.servers || {}) },
|
|
2450
|
+
domains: { ...(current?.domains || {}) },
|
|
2451
|
+
};
|
|
2452
|
+
const additionFs = addition?.domains?.fs;
|
|
2453
|
+
if (additionFs) {
|
|
2454
|
+
const existingFs = out.domains.fs || { readPaths: [], writePaths: [] };
|
|
2455
|
+
out.domains.fs = {
|
|
2456
|
+
readPaths: [
|
|
2457
|
+
...new Set([
|
|
2458
|
+
...(existingFs.readPaths || []),
|
|
2459
|
+
...(Array.isArray(additionFs.readPaths) ? additionFs.readPaths : []),
|
|
2460
|
+
]),
|
|
2461
|
+
],
|
|
2462
|
+
writePaths: [
|
|
2463
|
+
...new Set([
|
|
2464
|
+
...(existingFs.writePaths || []),
|
|
2465
|
+
...(Array.isArray(additionFs.writePaths)
|
|
2466
|
+
? additionFs.writePaths
|
|
2467
|
+
: []),
|
|
2468
|
+
]),
|
|
2469
|
+
],
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
return out;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/**
|
|
2476
|
+
* Async gate that escalates "no fs grant" denials to a JIT consent
|
|
2477
|
+
* prompt when `opts.enableJit` is true. On approval, merges the
|
|
2478
|
+
* decision's grant blob into the persisted grant and re-evaluates.
|
|
2479
|
+
*/
|
|
2480
|
+
async function gateFsCallWithJit$1(req, opts = {}) {
|
|
2481
|
+
const initial = gateFsCall$1(req);
|
|
2482
|
+
if (initial.allow) return initial;
|
|
2483
|
+
if (!opts.enableJit) return initial;
|
|
2484
|
+
if (!_isNoGrantDenial$1(initial.reason)) return initial;
|
|
2485
|
+
|
|
2486
|
+
let decision;
|
|
2487
|
+
try {
|
|
2488
|
+
decision = await requestApproval$1(
|
|
2489
|
+
{
|
|
2490
|
+
widgetId: req.widgetId,
|
|
2491
|
+
domain: "fs",
|
|
2492
|
+
action: req.action,
|
|
2493
|
+
args: req.args || {},
|
|
2494
|
+
},
|
|
2495
|
+
{ timeoutMs: opts.timeoutMs },
|
|
2496
|
+
);
|
|
2497
|
+
} catch (e) {
|
|
2498
|
+
return {
|
|
2499
|
+
allow: false,
|
|
2500
|
+
reason:
|
|
2501
|
+
"JIT consent " +
|
|
2502
|
+
(e && e.message ? e.message : "failed") +
|
|
2503
|
+
"; original denial: " +
|
|
2504
|
+
initial.reason,
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
if (!decision || decision.approve !== true) {
|
|
2509
|
+
return {
|
|
2510
|
+
allow: false,
|
|
2511
|
+
reason:
|
|
2512
|
+
"user declined JIT consent for widget '" +
|
|
2513
|
+
req.widgetId +
|
|
2514
|
+
"' calling fs '" +
|
|
2515
|
+
req.action +
|
|
2516
|
+
"'",
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
const filename = req.args?.filename || "*";
|
|
2521
|
+
const isWrite = isFsWriteAction(req.action);
|
|
2522
|
+
const addition =
|
|
2523
|
+
decision.granted && typeof decision.granted === "object"
|
|
2524
|
+
? decision.granted
|
|
2525
|
+
: {
|
|
2526
|
+
grantOrigin: "live",
|
|
2527
|
+
domains: {
|
|
2528
|
+
fs: {
|
|
2529
|
+
readPaths: !isWrite ? [filename] : [],
|
|
2530
|
+
writePaths: isWrite ? [filename] : [],
|
|
2531
|
+
},
|
|
2532
|
+
},
|
|
2533
|
+
};
|
|
2534
|
+
addition.grantOrigin = "live";
|
|
2535
|
+
|
|
2536
|
+
try {
|
|
2537
|
+
const current = getGrant$2(req.widgetId);
|
|
2538
|
+
const merged = _mergeFsGrant(current, addition);
|
|
2539
|
+
setGrant$2(req.widgetId, merged);
|
|
2540
|
+
} catch (e) {
|
|
2541
|
+
return {
|
|
2542
|
+
allow: false,
|
|
2543
|
+
reason:
|
|
2544
|
+
"JIT consent: failed to persist fs grant: " +
|
|
2545
|
+
(e && e.message ? e.message : String(e)),
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
return gateFsCall$1(req);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
var fsGate = {
|
|
2553
|
+
gateFsCall: gateFsCall$1,
|
|
2554
|
+
gateFsCallWithJit: gateFsCallWithJit$1,
|
|
2555
|
+
isFsWriteAction,
|
|
2556
|
+
WRITE_ACTIONS,
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
/**
|
|
2560
|
+
* securityFlags.js
|
|
2561
|
+
*
|
|
2562
|
+
* Centralized readers for the two boolean security flags that gate the
|
|
2563
|
+
* MCP allowlist stack:
|
|
2564
|
+
* - security.enforceWidgetMcpPermissions
|
|
2565
|
+
* - security.enableJitConsent
|
|
2566
|
+
*
|
|
2567
|
+
* **Default semantics: ON.** A missing settings.json, a missing
|
|
2568
|
+
* `security` block, or an undefined field all yield `true`. Only an
|
|
2569
|
+
* explicit `false` opts out. This is intentional — the security stack
|
|
2570
|
+
* is on by default; users have to actively disable it. The
|
|
2571
|
+
* Privacy & Security panel surfaces the toggles + a confirm-on-disable
|
|
2572
|
+
* dialog so the disable path is deliberate.
|
|
2573
|
+
*
|
|
2574
|
+
* The readers are pure functions of a settings object so the
|
|
2575
|
+
* default-on semantics are pinned by unit tests without touching the
|
|
2576
|
+
* filesystem. The callers in mcpController.js wrap these with
|
|
2577
|
+
* settings.json IO.
|
|
2578
|
+
*/
|
|
2579
|
+
|
|
2580
|
+
function readEnforceFlag$2(settings) {
|
|
2581
|
+
return settings?.security?.enforceWidgetMcpPermissions !== false;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
function readJitFlag$2(settings) {
|
|
2585
|
+
return settings?.security?.enableJitConsent !== false;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
var securityFlags = { readEnforceFlag: readEnforceFlag$2, readJitFlag: readJitFlag$2 };
|
|
2589
|
+
|
|
1826
2590
|
/**
|
|
1827
2591
|
* safeJsExecutor.js
|
|
1828
2592
|
*
|
|
@@ -1866,192 +2630,201 @@ var safePath_1 = {
|
|
|
1866
2630
|
* while QuickJS is executing synchronously.
|
|
1867
2631
|
*/
|
|
1868
2632
|
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2633
|
+
var safeJsExecutor;
|
|
2634
|
+
var hasRequiredSafeJsExecutor;
|
|
2635
|
+
|
|
2636
|
+
function requireSafeJsExecutor () {
|
|
2637
|
+
if (hasRequiredSafeJsExecutor) return safeJsExecutor;
|
|
2638
|
+
hasRequiredSafeJsExecutor = 1;
|
|
2639
|
+
|
|
2640
|
+
// quickjs-emscripten is loaded lazily inside getModule() rather than at
|
|
2641
|
+
// module top. Reason: when transform.js (which lives in the same dir)
|
|
2642
|
+
// does require("./safeJsExecutor") and rollup-plugin-commonjs sees a
|
|
2643
|
+
// statically-required external (`quickjs-emscripten`) inside that file,
|
|
2644
|
+
// it can mark the relative import as transitive-external and then fail
|
|
2645
|
+
// to resolve back. Deferring the require breaks the static-analysis
|
|
2646
|
+
// chain so the rollup build resolves cleanly.
|
|
2647
|
+
const DEFAULT_TIMEOUT_MS = 1000;
|
|
2648
|
+
const DEFAULT_MEMORY_BYTES = 32 * 1024 * 1024; // 32 MB
|
|
2649
|
+
|
|
2650
|
+
let _modulePromise = null;
|
|
2651
|
+
function getModule() {
|
|
2652
|
+
if (!_modulePromise) {
|
|
2653
|
+
const { getQuickJS } = require$$0$3;
|
|
2654
|
+
_modulePromise = getQuickJS();
|
|
2655
|
+
}
|
|
2656
|
+
return _modulePromise;
|
|
2657
|
+
}
|
|
1878
2658
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2659
|
+
function injectInputs(vm, args, inputs) {
|
|
2660
|
+
if (!Array.isArray(args) || !Array.isArray(inputs)) {
|
|
2661
|
+
throw new Error(
|
|
2662
|
+
"safeJsExecutor: args and inputs must be arrays of equal length",
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
if (args.length !== inputs.length) {
|
|
2666
|
+
throw new Error("safeJsExecutor: args.length must equal inputs.length");
|
|
2667
|
+
}
|
|
2668
|
+
for (let i = 0; i < args.length; i++) {
|
|
2669
|
+
const name = args[i];
|
|
2670
|
+
if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
2671
|
+
throw new Error("safeJsExecutor: arg names must be valid JS identifiers");
|
|
2672
|
+
}
|
|
2673
|
+
const json = JSON.stringify(inputs[i]);
|
|
2674
|
+
// Use evalCode to materialize the JSON-typed value inside the
|
|
2675
|
+
// VM. For undefined inputs, JSON.stringify returns undefined
|
|
2676
|
+
// (not a string) — fall through to evaluating the literal
|
|
2677
|
+
// `undefined`.
|
|
2678
|
+
const literal = json === undefined ? "undefined" : json;
|
|
2679
|
+
const result = vm.evalCode(`(${literal})`);
|
|
2680
|
+
if (result.error) {
|
|
2681
|
+
const err = vm.dump(result.error);
|
|
2682
|
+
result.error.dispose();
|
|
2683
|
+
throw new Error(
|
|
2684
|
+
`safeJsExecutor: failed to inject "${name}": ${
|
|
2685
|
+
err && err.message ? err.message : err
|
|
2686
|
+
}`,
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
vm.setProp(vm.global, name, result.value);
|
|
2690
|
+
result.value.dispose();
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
1887
2693
|
|
|
1888
|
-
function
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
);
|
|
1893
|
-
}
|
|
1894
|
-
if (args.length !== inputs.length) {
|
|
1895
|
-
throw new Error("safeJsExecutor: args.length must equal inputs.length");
|
|
1896
|
-
}
|
|
1897
|
-
for (let i = 0; i < args.length; i++) {
|
|
1898
|
-
const name = args[i];
|
|
1899
|
-
if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
1900
|
-
throw new Error("safeJsExecutor: arg names must be valid JS identifiers");
|
|
1901
|
-
}
|
|
1902
|
-
const json = JSON.stringify(inputs[i]);
|
|
1903
|
-
// Use evalCode to materialize the JSON-typed value inside the
|
|
1904
|
-
// VM. For undefined inputs, JSON.stringify returns undefined
|
|
1905
|
-
// (not a string) — fall through to evaluating the literal
|
|
1906
|
-
// `undefined`.
|
|
1907
|
-
const literal = json === undefined ? "undefined" : json;
|
|
1908
|
-
const result = vm.evalCode(`(${literal})`);
|
|
1909
|
-
if (result.error) {
|
|
1910
|
-
const err = vm.dump(result.error);
|
|
1911
|
-
result.error.dispose();
|
|
1912
|
-
throw new Error(
|
|
1913
|
-
`safeJsExecutor: failed to inject "${name}": ${
|
|
1914
|
-
err && err.message ? err.message : err
|
|
1915
|
-
}`,
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
vm.setProp(vm.global, name, result.value);
|
|
1919
|
-
result.value.dispose();
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
2694
|
+
function setDeadline(vm, timeoutMs) {
|
|
2695
|
+
const deadline = Date.now() + Math.max(1, timeoutMs);
|
|
2696
|
+
vm.runtime.setInterruptHandler(() => Date.now() > deadline);
|
|
2697
|
+
}
|
|
1922
2698
|
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2699
|
+
function buildWrappedBody(args, body) {
|
|
2700
|
+
// Wrap the user body in an IIFE so `return` works at the body level
|
|
2701
|
+
// (matching the dynamic-function-constructor semantics this is
|
|
2702
|
+
// replacing).
|
|
2703
|
+
return `(function(${args.join(",")}){${body}})(${args.join(",")})`;
|
|
2704
|
+
}
|
|
1927
2705
|
|
|
1928
|
-
function
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2706
|
+
async function runOnce({
|
|
2707
|
+
body,
|
|
2708
|
+
args = [],
|
|
2709
|
+
inputs = [],
|
|
2710
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
2711
|
+
memoryBytes = DEFAULT_MEMORY_BYTES,
|
|
2712
|
+
}) {
|
|
2713
|
+
if (typeof body !== "string" || !body.trim()) {
|
|
2714
|
+
return { error: "body must be a non-empty string" };
|
|
2715
|
+
}
|
|
2716
|
+
const QuickJS = await getModule();
|
|
2717
|
+
const vm = QuickJS.newContext();
|
|
2718
|
+
try {
|
|
2719
|
+
vm.runtime.setMemoryLimit(memoryBytes);
|
|
2720
|
+
vm.runtime.setMaxStackSize(1024 * 1024);
|
|
2721
|
+
setDeadline(vm, timeoutMs);
|
|
1934
2722
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
inputs = [],
|
|
1939
|
-
timeoutMs = DEFAULT_TIMEOUT_MS$1,
|
|
1940
|
-
memoryBytes = DEFAULT_MEMORY_BYTES,
|
|
1941
|
-
}) {
|
|
1942
|
-
if (typeof body !== "string" || !body.trim()) {
|
|
1943
|
-
return { error: "body must be a non-empty string" };
|
|
1944
|
-
}
|
|
1945
|
-
const QuickJS = await getModule();
|
|
1946
|
-
const vm = QuickJS.newContext();
|
|
1947
|
-
try {
|
|
1948
|
-
vm.runtime.setMemoryLimit(memoryBytes);
|
|
1949
|
-
vm.runtime.setMaxStackSize(1024 * 1024);
|
|
1950
|
-
setDeadline(vm, timeoutMs);
|
|
2723
|
+
injectInputs(vm, args, inputs);
|
|
2724
|
+
const wrapped = buildWrappedBody(args, body);
|
|
2725
|
+
const result = vm.evalCode(wrapped);
|
|
1951
2726
|
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2727
|
+
if (result.error) {
|
|
2728
|
+
const err = vm.dump(result.error);
|
|
2729
|
+
result.error.dispose();
|
|
2730
|
+
return {
|
|
2731
|
+
error:
|
|
2732
|
+
err && err.message
|
|
2733
|
+
? String(err.message)
|
|
2734
|
+
: typeof err === "string"
|
|
2735
|
+
? err
|
|
2736
|
+
: JSON.stringify(err),
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
const value = vm.dump(result.value);
|
|
2740
|
+
result.value.dispose();
|
|
2741
|
+
return { value };
|
|
2742
|
+
} catch (e) {
|
|
2743
|
+
return { error: e && e.message ? e.message : String(e) };
|
|
2744
|
+
} finally {
|
|
2745
|
+
vm.dispose();
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
1955
2748
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2749
|
+
async function createCompiled({
|
|
2750
|
+
body,
|
|
2751
|
+
args = [],
|
|
2752
|
+
memoryBytes = DEFAULT_MEMORY_BYTES,
|
|
2753
|
+
}) {
|
|
2754
|
+
if (typeof body !== "string" || !body.trim()) {
|
|
2755
|
+
throw new Error("body must be a non-empty string");
|
|
2756
|
+
}
|
|
2757
|
+
const QuickJS = await getModule();
|
|
2758
|
+
const vm = QuickJS.newContext();
|
|
2759
|
+
vm.runtime.setMemoryLimit(memoryBytes);
|
|
2760
|
+
vm.runtime.setMaxStackSize(1024 * 1024);
|
|
2761
|
+
|
|
2762
|
+
// Define the user function once on the VM globals so subsequent
|
|
2763
|
+
// run() calls can invoke it with fresh args without re-parsing.
|
|
2764
|
+
const define = vm.evalCode(
|
|
2765
|
+
`globalThis.__userFn = function(${args.join(",")}){${body}};`,
|
|
2766
|
+
);
|
|
2767
|
+
if (define.error) {
|
|
2768
|
+
const err = vm.dump(define.error);
|
|
2769
|
+
define.error.dispose();
|
|
2770
|
+
vm.dispose();
|
|
2771
|
+
throw new Error(
|
|
2772
|
+
"safeJsExecutor: compile failed: " +
|
|
2773
|
+
(err && err.message ? err.message : JSON.stringify(err)),
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
define.value.dispose();
|
|
1977
2777
|
|
|
1978
|
-
|
|
1979
|
-
body,
|
|
1980
|
-
args = [],
|
|
1981
|
-
memoryBytes = DEFAULT_MEMORY_BYTES,
|
|
1982
|
-
}) {
|
|
1983
|
-
if (typeof body !== "string" || !body.trim()) {
|
|
1984
|
-
throw new Error("body must be a non-empty string");
|
|
1985
|
-
}
|
|
1986
|
-
const QuickJS = await getModule();
|
|
1987
|
-
const vm = QuickJS.newContext();
|
|
1988
|
-
vm.runtime.setMemoryLimit(memoryBytes);
|
|
1989
|
-
vm.runtime.setMaxStackSize(1024 * 1024);
|
|
1990
|
-
|
|
1991
|
-
// Define the user function once on the VM globals so subsequent
|
|
1992
|
-
// run() calls can invoke it with fresh args without re-parsing.
|
|
1993
|
-
const define = vm.evalCode(
|
|
1994
|
-
`globalThis.__userFn = function(${args.join(",")}){${body}};`,
|
|
1995
|
-
);
|
|
1996
|
-
if (define.error) {
|
|
1997
|
-
const err = vm.dump(define.error);
|
|
1998
|
-
define.error.dispose();
|
|
1999
|
-
vm.dispose();
|
|
2000
|
-
throw new Error(
|
|
2001
|
-
"safeJsExecutor: compile failed: " +
|
|
2002
|
-
(err && err.message ? err.message : JSON.stringify(err)),
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
define.value.dispose();
|
|
2778
|
+
let disposed = false;
|
|
2006
2779
|
|
|
2007
|
-
|
|
2780
|
+
return {
|
|
2781
|
+
run(inputs, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
2782
|
+
if (disposed) {
|
|
2783
|
+
return { error: "executor already disposed" };
|
|
2784
|
+
}
|
|
2785
|
+
try {
|
|
2786
|
+
setDeadline(vm, timeoutMs);
|
|
2787
|
+
const argList = inputs
|
|
2788
|
+
.map((v) => (v === undefined ? "undefined" : JSON.stringify(v)))
|
|
2789
|
+
.join(",");
|
|
2790
|
+
const result = vm.evalCode(`__userFn(${argList})`);
|
|
2791
|
+
if (result.error) {
|
|
2792
|
+
const err = vm.dump(result.error);
|
|
2793
|
+
result.error.dispose();
|
|
2794
|
+
return {
|
|
2795
|
+
error:
|
|
2796
|
+
err && err.message
|
|
2797
|
+
? String(err.message)
|
|
2798
|
+
: typeof err === "string"
|
|
2799
|
+
? err
|
|
2800
|
+
: JSON.stringify(err),
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
const value = vm.dump(result.value);
|
|
2804
|
+
result.value.dispose();
|
|
2805
|
+
return { value };
|
|
2806
|
+
} catch (e) {
|
|
2807
|
+
return { error: e && e.message ? e.message : String(e) };
|
|
2808
|
+
}
|
|
2809
|
+
},
|
|
2810
|
+
dispose() {
|
|
2811
|
+
if (!disposed) {
|
|
2812
|
+
disposed = true;
|
|
2813
|
+
vm.dispose();
|
|
2814
|
+
}
|
|
2815
|
+
},
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2008
2818
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
const argList = inputs
|
|
2017
|
-
.map((v) => (v === undefined ? "undefined" : JSON.stringify(v)))
|
|
2018
|
-
.join(",");
|
|
2019
|
-
const result = vm.evalCode(`__userFn(${argList})`);
|
|
2020
|
-
if (result.error) {
|
|
2021
|
-
const err = vm.dump(result.error);
|
|
2022
|
-
result.error.dispose();
|
|
2023
|
-
return {
|
|
2024
|
-
error:
|
|
2025
|
-
err && err.message
|
|
2026
|
-
? String(err.message)
|
|
2027
|
-
: typeof err === "string"
|
|
2028
|
-
? err
|
|
2029
|
-
: JSON.stringify(err),
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
const value = vm.dump(result.value);
|
|
2033
|
-
result.value.dispose();
|
|
2034
|
-
return { value };
|
|
2035
|
-
} catch (e) {
|
|
2036
|
-
return { error: e && e.message ? e.message : String(e) };
|
|
2037
|
-
}
|
|
2038
|
-
},
|
|
2039
|
-
dispose() {
|
|
2040
|
-
if (!disposed) {
|
|
2041
|
-
disposed = true;
|
|
2042
|
-
vm.dispose();
|
|
2043
|
-
}
|
|
2044
|
-
},
|
|
2045
|
-
};
|
|
2819
|
+
safeJsExecutor = {
|
|
2820
|
+
runOnce,
|
|
2821
|
+
createCompiled,
|
|
2822
|
+
DEFAULT_TIMEOUT_MS,
|
|
2823
|
+
DEFAULT_MEMORY_BYTES,
|
|
2824
|
+
};
|
|
2825
|
+
return safeJsExecutor;
|
|
2046
2826
|
}
|
|
2047
2827
|
|
|
2048
|
-
var safeJsExecutor$1 = {
|
|
2049
|
-
runOnce,
|
|
2050
|
-
createCompiled,
|
|
2051
|
-
DEFAULT_TIMEOUT_MS: DEFAULT_TIMEOUT_MS$1,
|
|
2052
|
-
DEFAULT_MEMORY_BYTES,
|
|
2053
|
-
};
|
|
2054
|
-
|
|
2055
2828
|
/**
|
|
2056
2829
|
* Utils/tranaform
|
|
2057
2830
|
* Main gial is to transform a file of data into another form (CSV -> Json for example)
|
|
@@ -2060,462 +2833,514 @@ var safeJsExecutor$1 = {
|
|
|
2060
2833
|
* - CSV -> JSON
|
|
2061
2834
|
*/
|
|
2062
2835
|
|
|
2063
|
-
var
|
|
2064
|
-
var
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
var
|
|
2070
|
-
|
|
2071
|
-
const
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
const
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
_transform(value, encoding, callback) {
|
|
2089
|
-
this.push(JSON.parse(value));
|
|
2090
|
-
callback();
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
let Transform$1 = class Transform {
|
|
2095
|
-
constructor() {
|
|
2096
|
-
console.log("constructor");
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
/**
|
|
2100
|
-
* readLinesFromFile
|
|
2101
|
-
*
|
|
2102
|
-
* If this is a json file we should check in and remove the newLines, and then
|
|
2103
|
-
* add commas between records potentially...
|
|
2104
|
-
* @param {*} win
|
|
2105
|
-
* @param {*} filepath
|
|
2106
|
-
* @param {*} linesCount
|
|
2107
|
-
* @param {*} callbackEvent
|
|
2108
|
-
* @returns
|
|
2109
|
-
*/
|
|
2110
|
-
readLinesFromFile = (
|
|
2111
|
-
win,
|
|
2112
|
-
filepath,
|
|
2113
|
-
linesCount = 100,
|
|
2114
|
-
callbackEvent = null,
|
|
2115
|
-
) => {
|
|
2116
|
-
return new Promise((resolve, reject) => {
|
|
2117
|
-
try {
|
|
2118
|
-
let count = 0;
|
|
2119
|
-
let lines = [];
|
|
2836
|
+
var transform;
|
|
2837
|
+
var hasRequiredTransform;
|
|
2838
|
+
|
|
2839
|
+
function requireTransform () {
|
|
2840
|
+
if (hasRequiredTransform) return transform;
|
|
2841
|
+
hasRequiredTransform = 1;
|
|
2842
|
+
var fs = require$$0$2;
|
|
2843
|
+
var readline = require$$1$2;
|
|
2844
|
+
const xtreamer = require$$2;
|
|
2845
|
+
var xmlParser = require$$3$1;
|
|
2846
|
+
var JSONStream = require$$4;
|
|
2847
|
+
const stream = require$$5;
|
|
2848
|
+
var csv = require$$6;
|
|
2849
|
+
const path = require$$1$1;
|
|
2850
|
+
const { app } = require$$0$1;
|
|
2851
|
+
const { ensureDirectoryExistence } = file;
|
|
2852
|
+
const safeJsExecutor = requireSafeJsExecutor();
|
|
2853
|
+
|
|
2854
|
+
const TRANSFORM_APP_NAME = "Dashboard";
|
|
2855
|
+
const MAX_MAPPING_BODY_SIZE = 10240; // 10KB limit for mapping function body
|
|
2856
|
+
// Per-record execution limit. Each mapping function call inside the
|
|
2857
|
+
// streaming transform must complete within this budget; a runaway loop
|
|
2858
|
+
// in the user's mapping triggers an "interrupted" error and the record
|
|
2859
|
+
// is skipped rather than blocking the whole stream.
|
|
2860
|
+
const PER_RECORD_TIMEOUT_MS = 1000;
|
|
2120
2861
|
|
|
2121
|
-
|
|
2122
|
-
|
|
2862
|
+
/**
|
|
2863
|
+
* XtreamerClientTransform
|
|
2864
|
+
* Custom Transform stream to parse the JSON from the XML to String operation
|
|
2865
|
+
*/
|
|
2866
|
+
class XtreamerClientTransform extends stream.Transform {
|
|
2867
|
+
_transform(value, encoding, callback) {
|
|
2868
|
+
this.push(JSON.parse(value));
|
|
2869
|
+
callback();
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2123
2872
|
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
});
|
|
2873
|
+
class Transform {
|
|
2874
|
+
constructor() {
|
|
2875
|
+
console.log("constructor");
|
|
2876
|
+
}
|
|
2129
2877
|
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2878
|
+
/**
|
|
2879
|
+
* readLinesFromFile
|
|
2880
|
+
*
|
|
2881
|
+
* If this is a json file we should check in and remove the newLines, and then
|
|
2882
|
+
* add commas between records potentially...
|
|
2883
|
+
* @param {*} win
|
|
2884
|
+
* @param {*} filepath
|
|
2885
|
+
* @param {*} linesCount
|
|
2886
|
+
* @param {*} callbackEvent
|
|
2887
|
+
* @returns
|
|
2888
|
+
*/
|
|
2889
|
+
readLinesFromFile = (
|
|
2890
|
+
win,
|
|
2891
|
+
filepath,
|
|
2892
|
+
linesCount = 100,
|
|
2893
|
+
callbackEvent = null,
|
|
2894
|
+
) => {
|
|
2895
|
+
return new Promise((resolve, reject) => {
|
|
2896
|
+
try {
|
|
2897
|
+
let count = 0;
|
|
2898
|
+
let lines = [];
|
|
2150
2899
|
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
file = "",
|
|
2154
|
-
objectCount = 10,
|
|
2155
|
-
callbackEvent = null,
|
|
2156
|
-
) => {
|
|
2157
|
-
return new Promise((resolve, reject) => {
|
|
2158
|
-
try {
|
|
2159
|
-
const parser = JSONStream$1.parse("*");
|
|
2160
|
-
const readStream = fs$e.createReadStream(file).pipe(parser);
|
|
2900
|
+
// can we aggregate potentially?
|
|
2901
|
+
let lineObject = [];
|
|
2161
2902
|
|
|
2162
|
-
|
|
2903
|
+
const readInterface = readline.createInterface({
|
|
2904
|
+
input: fs.createReadStream(filepath),
|
|
2905
|
+
output: process.stdout,
|
|
2906
|
+
console: false,
|
|
2907
|
+
});
|
|
2163
2908
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2909
|
+
readInterface.on("line", function (line) {
|
|
2910
|
+
if (count < linesCount) {
|
|
2911
|
+
//console.log(line);
|
|
2912
|
+
lines.push(line);
|
|
2913
|
+
count++;
|
|
2914
|
+
if (callbackEvent !== null) {
|
|
2915
|
+
win.webContents.send(callbackEvent, {
|
|
2916
|
+
line,
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
} else {
|
|
2920
|
+
readInterface.close();
|
|
2921
|
+
resolve(lines);
|
|
2922
|
+
}
|
|
2923
|
+
});
|
|
2924
|
+
} catch (e) {
|
|
2925
|
+
reject(e);
|
|
2926
|
+
}
|
|
2927
|
+
});
|
|
2928
|
+
};
|
|
2177
2929
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2930
|
+
readJSONFromFile = (
|
|
2931
|
+
win,
|
|
2932
|
+
file = "",
|
|
2933
|
+
objectCount = 10,
|
|
2934
|
+
callbackEvent = null,
|
|
2935
|
+
) => {
|
|
2936
|
+
return new Promise((resolve, reject) => {
|
|
2937
|
+
try {
|
|
2938
|
+
const parser = JSONStream.parse("*");
|
|
2939
|
+
const readStream = fs.createReadStream(file).pipe(parser);
|
|
2181
2940
|
|
|
2182
|
-
|
|
2183
|
-
resolve("Complete");
|
|
2184
|
-
});
|
|
2185
|
-
} catch (e) {
|
|
2186
|
-
console.log("read json error ", e);
|
|
2187
|
-
reject(e);
|
|
2188
|
-
}
|
|
2189
|
-
});
|
|
2190
|
-
};
|
|
2941
|
+
let count = 0;
|
|
2191
2942
|
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
})
|
|
2206
|
-
.catch((e) => reject(e));
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2943
|
+
readStream.on("data", (data) => {
|
|
2944
|
+
//console.log(data);
|
|
2945
|
+
if (callbackEvent !== null) {
|
|
2946
|
+
win.webContents.send(callbackEvent, {
|
|
2947
|
+
data: JSON.stringify(data),
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
if (objectCount !== null && count < objectCount) {
|
|
2951
|
+
readStream.destroy();
|
|
2952
|
+
resolve("complete");
|
|
2953
|
+
}
|
|
2954
|
+
count++;
|
|
2955
|
+
});
|
|
2209
2956
|
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
const xmlFileReadStream = fs$e.createReadStream(filepath);
|
|
2957
|
+
readStream.on("error", (e) => {
|
|
2958
|
+
reject(e);
|
|
2959
|
+
});
|
|
2214
2960
|
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2961
|
+
readStream.on("end", (data) => {
|
|
2962
|
+
resolve("Complete");
|
|
2963
|
+
});
|
|
2964
|
+
} catch (e) {
|
|
2965
|
+
console.log("read json error ", e);
|
|
2966
|
+
reject(e);
|
|
2967
|
+
}
|
|
2968
|
+
});
|
|
2969
|
+
};
|
|
2219
2970
|
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2971
|
+
async parseXMLString(data) {
|
|
2972
|
+
let xmlText = data.toString().replace("\ufeff", "");
|
|
2973
|
+
return new Promise((resolve, reject) => {
|
|
2974
|
+
xmlParser
|
|
2975
|
+
.parseStringPromise(xmlText, {
|
|
2976
|
+
trim: true,
|
|
2977
|
+
compact: true,
|
|
2978
|
+
ignoreComment: true,
|
|
2979
|
+
ignoreDoctype: true,
|
|
2980
|
+
})
|
|
2981
|
+
.then((data) => {
|
|
2982
|
+
// resolve with the comma
|
|
2983
|
+
resolve(JSON.stringify(data) + ",");
|
|
2984
|
+
})
|
|
2985
|
+
.catch((e) => reject(e));
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2223
2988
|
|
|
2224
|
-
|
|
2225
|
-
|
|
2989
|
+
parseXMLStream = (filepath, outpath, start) => {
|
|
2990
|
+
return new Promise((resolve, reject) => {
|
|
2991
|
+
try {
|
|
2992
|
+
const xmlFileReadStream = fs.createReadStream(filepath);
|
|
2226
2993
|
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
simple: false,
|
|
2232
|
-
max_xml_size: 50000000, // 10000000
|
|
2233
|
-
};
|
|
2994
|
+
xmlFileReadStream.on("end", () => {
|
|
2995
|
+
writeStream.write("\n]");
|
|
2996
|
+
resolve("Read End");
|
|
2997
|
+
});
|
|
2234
2998
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
transformer: this.parseXMLString, // returns Promise
|
|
2239
|
-
max_xml_size: 50000000,
|
|
2240
|
-
},
|
|
2241
|
-
options,
|
|
2242
|
-
).on("error", (e) => {
|
|
2243
|
-
console.log(e);
|
|
2244
|
-
reject(e);
|
|
2245
|
-
});
|
|
2999
|
+
xmlFileReadStream.on("finished", () => {
|
|
3000
|
+
resolve("Read Finish");
|
|
3001
|
+
});
|
|
2246
3002
|
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
.pipe(new XtreamerClientTransform())
|
|
2250
|
-
.pipe(writeStream)
|
|
2251
|
-
.on("end", () => {
|
|
2252
|
-
console.log("ended");
|
|
2253
|
-
})
|
|
2254
|
-
.on("finish", () => {
|
|
2255
|
-
console.log("finished pipe");
|
|
2256
|
-
});
|
|
2257
|
-
} catch (e) {
|
|
2258
|
-
console.log(e);
|
|
2259
|
-
reject(e);
|
|
2260
|
-
}
|
|
2261
|
-
});
|
|
2262
|
-
};
|
|
3003
|
+
const writeStream = fs.createWriteStream(outpath);
|
|
3004
|
+
writeStream.write("[\n");
|
|
2263
3005
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
callbackEvent = null,
|
|
2272
|
-
limit = null,
|
|
2273
|
-
) => {
|
|
2274
|
-
return new Promise((resolve, reject) => {
|
|
2275
|
-
try {
|
|
2276
|
-
const readStream = fs$e
|
|
2277
|
-
.createReadStream(filepath)
|
|
2278
|
-
.pipe(csv({ separator: delimiter }));
|
|
2279
|
-
const writeStream = fs$e.createWriteStream(outpath);
|
|
2280
|
-
|
|
2281
|
-
let canParse = true;
|
|
3006
|
+
const options = {
|
|
3007
|
+
headers: { Accept: "application/xml" },
|
|
3008
|
+
resolveWithFullResponse: true,
|
|
3009
|
+
json: false,
|
|
3010
|
+
simple: false,
|
|
3011
|
+
max_xml_size: 50000000, // 10000000
|
|
3012
|
+
};
|
|
2282
3013
|
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
3014
|
+
const xtreamerTransform = xtreamer(
|
|
3015
|
+
start,
|
|
3016
|
+
{
|
|
3017
|
+
transformer: this.parseXMLString, // returns Promise
|
|
3018
|
+
max_xml_size: 50000000,
|
|
3019
|
+
},
|
|
3020
|
+
options,
|
|
3021
|
+
).on("error", (e) => {
|
|
3022
|
+
console.log(e);
|
|
3023
|
+
reject(e);
|
|
3024
|
+
});
|
|
2287
3025
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
3026
|
+
xmlFileReadStream
|
|
3027
|
+
.pipe(xtreamerTransform)
|
|
3028
|
+
.pipe(new XtreamerClientTransform())
|
|
3029
|
+
.pipe(writeStream)
|
|
3030
|
+
.on("end", () => {
|
|
3031
|
+
console.log("ended");
|
|
3032
|
+
})
|
|
3033
|
+
.on("finish", () => {
|
|
3034
|
+
console.log("finished pipe");
|
|
3035
|
+
});
|
|
3036
|
+
} catch (e) {
|
|
3037
|
+
console.log(e);
|
|
3038
|
+
reject(e);
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
};
|
|
2303
3042
|
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
3043
|
+
parseCSVStream = (
|
|
3044
|
+
filepath,
|
|
3045
|
+
outpath,
|
|
3046
|
+
delimiter = ",",
|
|
3047
|
+
objectIdKey = null,
|
|
3048
|
+
headers = null, // optional array of headings to grab
|
|
3049
|
+
win = null,
|
|
3050
|
+
callbackEvent = null,
|
|
3051
|
+
limit = null,
|
|
3052
|
+
) => {
|
|
3053
|
+
return new Promise((resolve, reject) => {
|
|
3054
|
+
try {
|
|
3055
|
+
const readStream = fs
|
|
3056
|
+
.createReadStream(filepath)
|
|
3057
|
+
.pipe(csv({ separator: delimiter }));
|
|
3058
|
+
const writeStream = fs.createWriteStream(outpath);
|
|
2312
3059
|
|
|
2313
|
-
|
|
2314
|
-
writeStream.write("]");
|
|
2315
|
-
readStream.destroy();
|
|
2316
|
-
resolve("Complete");
|
|
2317
|
-
});
|
|
2318
|
-
} catch (e) {
|
|
2319
|
-
reject(e);
|
|
2320
|
-
}
|
|
2321
|
-
});
|
|
2322
|
-
};
|
|
3060
|
+
let canParse = true;
|
|
2323
3061
|
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
*
|
|
2329
|
-
* @param {*} win
|
|
2330
|
-
* @param {*} filepath
|
|
2331
|
-
* @param {*} outFilepath
|
|
2332
|
-
* @param {*} mapping
|
|
2333
|
-
*/
|
|
2334
|
-
transformFileToFile = (
|
|
2335
|
-
win,
|
|
2336
|
-
filepath,
|
|
2337
|
-
outFilepath,
|
|
2338
|
-
mappingFunctionBody,
|
|
2339
|
-
args = ["refObj", "index"],
|
|
2340
|
-
callbackEvent = null,
|
|
2341
|
-
) => {
|
|
2342
|
-
return new Promise((resolve, reject) => {
|
|
2343
|
-
// Validate mappingFunctionBody is a non-empty string
|
|
2344
|
-
if (
|
|
2345
|
-
typeof mappingFunctionBody !== "string" ||
|
|
2346
|
-
!mappingFunctionBody.trim()
|
|
2347
|
-
) {
|
|
2348
|
-
return reject(
|
|
2349
|
-
new Error("mappingFunctionBody must be a non-empty string"),
|
|
2350
|
-
);
|
|
2351
|
-
}
|
|
3062
|
+
// separators for JSON
|
|
3063
|
+
let sep = "";
|
|
3064
|
+
let count = 0;
|
|
3065
|
+
writeStream.write("[\n");
|
|
2352
3066
|
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
3067
|
+
readStream.on("data", (item) => {
|
|
3068
|
+
if (count > 0) {
|
|
3069
|
+
sep = ",\n";
|
|
3070
|
+
}
|
|
3071
|
+
// if we have specified a limit...
|
|
3072
|
+
if (limit !== null && limit <= count) {
|
|
3073
|
+
canParse = false;
|
|
3074
|
+
writeStream.write("]");
|
|
3075
|
+
readStream.destroy();
|
|
3076
|
+
resolve("Complete");
|
|
3077
|
+
}
|
|
3078
|
+
if (canParse === true) {
|
|
3079
|
+
item["objectID"] = item[objectIdKey];
|
|
3080
|
+
writeStream.write(sep + JSON.stringify(item));
|
|
3081
|
+
count++;
|
|
3082
|
+
|
|
3083
|
+
if (win !== null) {
|
|
3084
|
+
//console.log("have win", count, callbackEvent);
|
|
3085
|
+
win.webContents.send(callbackEvent, {
|
|
3086
|
+
count,
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
2363
3091
|
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
3092
|
+
readStream.on("end", () => {
|
|
3093
|
+
writeStream.write("]");
|
|
3094
|
+
readStream.destroy();
|
|
3095
|
+
resolve("Complete");
|
|
3096
|
+
});
|
|
3097
|
+
} catch (e) {
|
|
3098
|
+
reject(e);
|
|
3099
|
+
}
|
|
3100
|
+
});
|
|
3101
|
+
};
|
|
2368
3102
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
3103
|
+
/**
|
|
3104
|
+
* transformFileToFile
|
|
3105
|
+
* We want to convert a format from a file into a different format
|
|
3106
|
+
* based on the mapper we have provided
|
|
3107
|
+
*
|
|
3108
|
+
* @param {*} win
|
|
3109
|
+
* @param {*} filepath
|
|
3110
|
+
* @param {*} outFilepath
|
|
3111
|
+
* @param {*} mapping
|
|
3112
|
+
*/
|
|
3113
|
+
transformFileToFile = (
|
|
3114
|
+
win,
|
|
3115
|
+
filepath,
|
|
3116
|
+
outFilepath,
|
|
3117
|
+
mappingFunctionBody,
|
|
3118
|
+
args = ["refObj", "index"],
|
|
3119
|
+
callbackEvent = null,
|
|
3120
|
+
) => {
|
|
3121
|
+
return new Promise((resolve, reject) => {
|
|
3122
|
+
// Validate mappingFunctionBody is a non-empty string
|
|
3123
|
+
if (
|
|
3124
|
+
typeof mappingFunctionBody !== "string" ||
|
|
3125
|
+
!mappingFunctionBody.trim()
|
|
3126
|
+
) {
|
|
3127
|
+
return reject(
|
|
3128
|
+
new Error("mappingFunctionBody must be a non-empty string"),
|
|
3129
|
+
);
|
|
3130
|
+
}
|
|
2383
3131
|
|
|
2384
|
-
|
|
2385
|
-
|
|
3132
|
+
// Enforce size limit on mapping function body
|
|
3133
|
+
if (mappingFunctionBody.length > MAX_MAPPING_BODY_SIZE) {
|
|
3134
|
+
return reject(
|
|
3135
|
+
new Error(
|
|
3136
|
+
"mappingFunctionBody exceeds maximum size of " +
|
|
3137
|
+
MAX_MAPPING_BODY_SIZE +
|
|
3138
|
+
" bytes",
|
|
3139
|
+
),
|
|
3140
|
+
);
|
|
3141
|
+
}
|
|
2386
3142
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
// create the readStream to parse the large file (json)
|
|
2392
|
-
var readStream = fs$e.createReadStream(resolvedFilepath).pipe(parser);
|
|
3143
|
+
// Validate file paths are within app data directory
|
|
3144
|
+
const appDataDir = path.join(app.getPath("userData"), TRANSFORM_APP_NAME);
|
|
3145
|
+
const resolvedFilepath = path.resolve(filepath);
|
|
3146
|
+
const resolvedOutFilepath = path.resolve(outFilepath);
|
|
2393
3147
|
|
|
2394
|
-
|
|
3148
|
+
if (!resolvedFilepath.startsWith(appDataDir + path.sep)) {
|
|
3149
|
+
return reject(
|
|
3150
|
+
new Error(
|
|
3151
|
+
"Input file path must be within the application data directory",
|
|
3152
|
+
),
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
if (!resolvedOutFilepath.startsWith(appDataDir + path.sep)) {
|
|
3156
|
+
return reject(
|
|
3157
|
+
new Error(
|
|
3158
|
+
"Output file path must be within the application data directory",
|
|
3159
|
+
),
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
2395
3162
|
|
|
2396
|
-
|
|
3163
|
+
// JSON parser
|
|
3164
|
+
var parser = JSONStream.parse("*");
|
|
2397
3165
|
|
|
2398
|
-
|
|
2399
|
-
|
|
3166
|
+
if (!fs.existsSync(resolvedFilepath)) {
|
|
3167
|
+
return reject(new Error("File doesnt exist"));
|
|
3168
|
+
}
|
|
3169
|
+
console.log("file exists ", resolvedFilepath);
|
|
3170
|
+
// create the readStream to parse the large file (json)
|
|
3171
|
+
var readStream = fs.createReadStream(resolvedFilepath).pipe(parser);
|
|
3172
|
+
|
|
3173
|
+
ensureDirectoryExistence(resolvedOutFilepath);
|
|
3174
|
+
|
|
3175
|
+
var writeStream = fs.createWriteStream(resolvedOutFilepath);
|
|
3176
|
+
|
|
3177
|
+
let sep = "";
|
|
3178
|
+
let count = 0;
|
|
3179
|
+
|
|
3180
|
+
// Compile the user-supplied mapping function inside a QuickJS
|
|
3181
|
+
// sandbox. The previous implementation compiled the body with
|
|
3182
|
+
// full Node.js privileges (filesystem, network, process). The
|
|
3183
|
+
// sandbox gives the body a tiny pure-JS surface (Math, JSON,
|
|
3184
|
+
// Date, primitives) and no host globals — see
|
|
3185
|
+
// electron/utils/safeJsExecutor.js for full rationale.
|
|
3186
|
+
//
|
|
3187
|
+
// createCompiled is async (the WASM module must be loaded). Use
|
|
3188
|
+
// .then/.catch instead of await because we're inside a sync
|
|
3189
|
+
// Promise executor.
|
|
3190
|
+
safeJsExecutor
|
|
3191
|
+
.createCompiled({
|
|
3192
|
+
body: mappingFunctionBody,
|
|
3193
|
+
args,
|
|
3194
|
+
})
|
|
3195
|
+
.then((executor) => {
|
|
3196
|
+
startStreamingWithExecutor(executor);
|
|
3197
|
+
})
|
|
3198
|
+
.catch((e) => {
|
|
3199
|
+
reject(
|
|
3200
|
+
new Error(
|
|
3201
|
+
"mappingFunctionBody failed to compile: " +
|
|
3202
|
+
(e && e.message ? e.message : String(e)),
|
|
3203
|
+
),
|
|
3204
|
+
);
|
|
3205
|
+
});
|
|
2400
3206
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
// sandbox gives the body a tiny pure-JS surface (Math, JSON,
|
|
2405
|
-
// Date, primitives) and no host globals — see
|
|
2406
|
-
// electron/utils/safeJsExecutor.js for full rationale.
|
|
2407
|
-
//
|
|
2408
|
-
// createCompiled is async (the WASM module must be loaded). Use
|
|
2409
|
-
// .then/.catch instead of await because we're inside a sync
|
|
2410
|
-
// Promise executor.
|
|
2411
|
-
safeJsExecutor
|
|
2412
|
-
.createCompiled({
|
|
2413
|
-
body: mappingFunctionBody,
|
|
2414
|
-
args,
|
|
2415
|
-
})
|
|
2416
|
-
.then((executor) => {
|
|
2417
|
-
startStreamingWithExecutor(executor);
|
|
2418
|
-
})
|
|
2419
|
-
.catch((e) => {
|
|
2420
|
-
reject(
|
|
2421
|
-
new Error(
|
|
2422
|
-
"mappingFunctionBody failed to compile: " +
|
|
2423
|
-
(e && e.message ? e.message : String(e)),
|
|
2424
|
-
),
|
|
2425
|
-
);
|
|
2426
|
-
});
|
|
3207
|
+
function startStreamingWithExecutor(executor) {
|
|
3208
|
+
// begin the write stream
|
|
3209
|
+
writeStream.write("[\n");
|
|
2427
3210
|
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
3211
|
+
readStream.on("data", (data) => {
|
|
3212
|
+
try {
|
|
3213
|
+
if (count > 0) {
|
|
3214
|
+
sep = ",\n";
|
|
3215
|
+
}
|
|
2431
3216
|
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
count +
|
|
2447
|
-
": " +
|
|
2448
|
-
result.error,
|
|
2449
|
-
);
|
|
2450
|
-
return;
|
|
2451
|
-
}
|
|
3217
|
+
if (data) {
|
|
3218
|
+
const result = executor.run([data, count], PER_RECORD_TIMEOUT_MS);
|
|
3219
|
+
if (result.error) {
|
|
3220
|
+
// Don't block the stream on a single bad record — log,
|
|
3221
|
+
// skip, continue. Matches the previous try/catch in the
|
|
3222
|
+
// unsandboxed implementation.
|
|
3223
|
+
console.log(
|
|
3224
|
+
"[transform] mapping error on record " +
|
|
3225
|
+
count +
|
|
3226
|
+
": " +
|
|
3227
|
+
result.error,
|
|
3228
|
+
);
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
2452
3231
|
|
|
2453
|
-
|
|
3232
|
+
writeStream.write(sep + JSON.stringify(result.value));
|
|
2454
3233
|
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
3234
|
+
if (callbackEvent !== null && win !== null) {
|
|
3235
|
+
win.webContents.send(callbackEvent, { count });
|
|
3236
|
+
}
|
|
2458
3237
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
3238
|
+
count++;
|
|
3239
|
+
}
|
|
3240
|
+
} catch (e) {
|
|
3241
|
+
console.log(e.message);
|
|
3242
|
+
}
|
|
3243
|
+
});
|
|
2465
3244
|
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
3245
|
+
readStream.on("end", () => {
|
|
3246
|
+
writeStream.write("\n]");
|
|
3247
|
+
writeStream.close();
|
|
3248
|
+
executor.dispose();
|
|
3249
|
+
resolve("Complete: wrote " + count + " objects");
|
|
3250
|
+
});
|
|
2472
3251
|
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
3252
|
+
readStream.on("error", (err) => {
|
|
3253
|
+
console.log("read stream error transform ", err.message);
|
|
3254
|
+
executor.dispose();
|
|
3255
|
+
reject(err);
|
|
3256
|
+
});
|
|
3257
|
+
} // end startStreamingWithExecutor
|
|
3258
|
+
});
|
|
3259
|
+
};
|
|
2481
3260
|
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
3261
|
+
/**
|
|
3262
|
+
* sanitizeLine (line of file)
|
|
3263
|
+
* @param {String} line JSON data that we wish to sanitize
|
|
3264
|
+
* @returns String the sanitized String
|
|
3265
|
+
*/
|
|
3266
|
+
sanitizeLine = (line) => {
|
|
3267
|
+
try {
|
|
3268
|
+
let newLine = line;
|
|
2490
3269
|
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
3270
|
+
if (line.slice(-1) === ",") {
|
|
3271
|
+
newLine = newLine.slice(0, -1);
|
|
3272
|
+
}
|
|
2494
3273
|
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
3274
|
+
if (line.slice(0, 1) === "[") {
|
|
3275
|
+
newLine = newLine.slice(1);
|
|
3276
|
+
}
|
|
2498
3277
|
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
}
|
|
3278
|
+
return newLine;
|
|
3279
|
+
} catch (e) {
|
|
3280
|
+
return line;
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
2505
3284
|
|
|
2506
|
-
|
|
3285
|
+
transform = Transform;
|
|
3286
|
+
return transform;
|
|
3287
|
+
}
|
|
2507
3288
|
|
|
2508
|
-
const { app: app$
|
|
2509
|
-
var fs$
|
|
2510
|
-
const path$
|
|
3289
|
+
const { app: app$a } = require$$0$1;
|
|
3290
|
+
var fs$c = require$$0$2;
|
|
3291
|
+
const path$h = require$$1$1;
|
|
2511
3292
|
const events$5 = events$8;
|
|
2512
3293
|
const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
|
|
2513
3294
|
const { safePath: safePath$2, getAllowedRoots: getAllowedRoots$1 } = safePath_1;
|
|
3295
|
+
const { gateFsCall, gateFsCallWithJit } = fsGate;
|
|
3296
|
+
const { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 } = securityFlags;
|
|
3297
|
+
|
|
3298
|
+
// Reads the enforcement + JIT flags from settings.json. Mirrors the
|
|
3299
|
+
// helper in mcpController. The flag is shared across MCP and fs domains
|
|
3300
|
+
// — see Phase 2 plan for rationale (the cosmetic rename to a
|
|
3301
|
+
// domain-neutral name is a separate slice).
|
|
3302
|
+
function _loadFlags() {
|
|
3303
|
+
try {
|
|
3304
|
+
const settingsPath = path$h.join(
|
|
3305
|
+
app$a.getPath("userData"),
|
|
3306
|
+
appName$5,
|
|
3307
|
+
"settings.json",
|
|
3308
|
+
);
|
|
3309
|
+
if (!fs$c.existsSync(settingsPath)) return null;
|
|
3310
|
+
return JSON.parse(fs$c.readFileSync(settingsPath, "utf8"));
|
|
3311
|
+
} catch (_e) {
|
|
3312
|
+
return null;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
/**
|
|
3317
|
+
* Run the fs gate before a dataController handler does its work.
|
|
3318
|
+
* On deny, sends an error event to the renderer and returns false so
|
|
3319
|
+
* the caller can early-out. On allow, returns true.
|
|
3320
|
+
*
|
|
3321
|
+
* @returns {Promise<boolean>}
|
|
3322
|
+
*/
|
|
3323
|
+
async function _runFsGate(win, action, widgetId, args, errorEvent) {
|
|
3324
|
+
const settings = _loadFlags();
|
|
3325
|
+
if (!readEnforceFlag$1(settings)) return true; // gate disabled
|
|
3326
|
+
if (!widgetId) return true; // legacy callers without widgetId — see plan
|
|
3327
|
+
const gate = readJitFlag$1(settings)
|
|
3328
|
+
? await gateFsCallWithJit({ widgetId, action, args }, { enableJit: true })
|
|
3329
|
+
: gateFsCall({ widgetId, action, args });
|
|
3330
|
+
if (gate.allow) return true;
|
|
3331
|
+
if (win && errorEvent) {
|
|
3332
|
+
win.webContents.send(errorEvent, {
|
|
3333
|
+
success: false,
|
|
3334
|
+
message: "fs permission gate: " + gate.reason,
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
return false;
|
|
3338
|
+
}
|
|
2514
3339
|
|
|
2515
3340
|
// Convert Json to Csv
|
|
2516
|
-
const ObjectsToCsv = require$$
|
|
2517
|
-
const Transform =
|
|
2518
|
-
const https$3 = require$$
|
|
3341
|
+
const ObjectsToCsv = require$$8$1;
|
|
3342
|
+
const Transform = requireTransform();
|
|
3343
|
+
const https$3 = require$$10;
|
|
2519
3344
|
const appName$5 = "Dashboard";
|
|
2520
3345
|
|
|
2521
3346
|
const dataController$1 = {
|
|
@@ -2532,8 +3357,8 @@ const dataController$1 = {
|
|
|
2532
3357
|
// Validate the renderer-supplied filename is contained within
|
|
2533
3358
|
// the data directory. path.join doesn't reject `..` segments;
|
|
2534
3359
|
// safePath does.
|
|
2535
|
-
const candidate = path$
|
|
2536
|
-
app$
|
|
3360
|
+
const candidate = path$h.join(
|
|
3361
|
+
app$a.getPath("userData"),
|
|
2537
3362
|
appName$5,
|
|
2538
3363
|
appId,
|
|
2539
3364
|
"data",
|
|
@@ -2691,7 +3516,7 @@ const dataController$1 = {
|
|
|
2691
3516
|
// intent, plus realpath/symlink protection.
|
|
2692
3517
|
const resolvedFilepath = safePath$2(toFilepath, getAllowedRoots$1("data"));
|
|
2693
3518
|
|
|
2694
|
-
const writeStream = fs$
|
|
3519
|
+
const writeStream = fs$c.createWriteStream(resolvedFilepath);
|
|
2695
3520
|
|
|
2696
3521
|
https$3
|
|
2697
3522
|
.get(url, (resp) => {
|
|
@@ -2850,13 +3675,31 @@ const dataController$1 = {
|
|
|
2850
3675
|
* @param {*} append
|
|
2851
3676
|
* @param {*} returnEmpty
|
|
2852
3677
|
*/
|
|
2853
|
-
saveToFile: (
|
|
3678
|
+
saveToFile: async (
|
|
3679
|
+
win,
|
|
3680
|
+
data,
|
|
3681
|
+
filename,
|
|
3682
|
+
append,
|
|
3683
|
+
returnEmpty = {},
|
|
3684
|
+
widgetId = null,
|
|
3685
|
+
) => {
|
|
3686
|
+
// Phase 2 fs gate. Runs before safePath containment so JIT can
|
|
3687
|
+
// prompt the user without leaking path-shape information through
|
|
3688
|
+
// error timing. See electron/security/fsGate.js.
|
|
3689
|
+
const gateOk = await _runFsGate(
|
|
3690
|
+
win,
|
|
3691
|
+
"saveToFile",
|
|
3692
|
+
widgetId,
|
|
3693
|
+
{ filename },
|
|
3694
|
+
events$5.DATA_SAVE_TO_FILE_ERROR,
|
|
3695
|
+
);
|
|
3696
|
+
if (!gateOk) return;
|
|
2854
3697
|
try {
|
|
2855
3698
|
if (data) {
|
|
2856
3699
|
// Validate filename is contained within the data directory.
|
|
2857
3700
|
// path.join doesn't reject `..` segments; safePath does.
|
|
2858
|
-
const candidate = path$
|
|
2859
|
-
app$
|
|
3701
|
+
const candidate = path$h.join(
|
|
3702
|
+
app$a.getPath("userData"),
|
|
2860
3703
|
appName$5,
|
|
2861
3704
|
"data",
|
|
2862
3705
|
filename,
|
|
@@ -2944,12 +3787,21 @@ const dataController$1 = {
|
|
|
2944
3787
|
}
|
|
2945
3788
|
},
|
|
2946
3789
|
|
|
2947
|
-
readFromFile: (win, filename, returnIfEmpty = {}) => {
|
|
3790
|
+
readFromFile: async (win, filename, returnIfEmpty = {}, widgetId = null) => {
|
|
3791
|
+
// Phase 2 fs gate — same as saveToFile.
|
|
3792
|
+
const gateOk = await _runFsGate(
|
|
3793
|
+
win,
|
|
3794
|
+
"readFromFile",
|
|
3795
|
+
widgetId,
|
|
3796
|
+
{ filename },
|
|
3797
|
+
events$5.DATA_READ_FROM_FILE_ERROR,
|
|
3798
|
+
);
|
|
3799
|
+
if (!gateOk) return;
|
|
2948
3800
|
try {
|
|
2949
3801
|
if (filename) {
|
|
2950
3802
|
// filename to the pages file (live pages)
|
|
2951
|
-
const fromFilename = path$
|
|
2952
|
-
app$
|
|
3803
|
+
const fromFilename = path$h.join(
|
|
3804
|
+
app$a.getPath("userData"),
|
|
2953
3805
|
appName$5,
|
|
2954
3806
|
"data",
|
|
2955
3807
|
filename,
|
|
@@ -3029,9 +3881,9 @@ var dataController_1 = dataController$1;
|
|
|
3029
3881
|
* settingsController
|
|
3030
3882
|
*/
|
|
3031
3883
|
|
|
3032
|
-
const { app: app$
|
|
3033
|
-
const path$
|
|
3034
|
-
const fs$
|
|
3884
|
+
const { app: app$9 } = require$$0$1;
|
|
3885
|
+
const path$g = require$$1$1;
|
|
3886
|
+
const fs$b = require$$0$2;
|
|
3035
3887
|
const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
|
|
3036
3888
|
|
|
3037
3889
|
const configFilename$3 = "settings.json";
|
|
@@ -3039,15 +3891,15 @@ const appName$4 = "Dashboard";
|
|
|
3039
3891
|
|
|
3040
3892
|
// Helper function to recursively copy directory
|
|
3041
3893
|
function copyDirectory(source, destination) {
|
|
3042
|
-
if (!fs$
|
|
3043
|
-
fs$
|
|
3894
|
+
if (!fs$b.existsSync(destination)) {
|
|
3895
|
+
fs$b.mkdirSync(destination, { recursive: true });
|
|
3044
3896
|
}
|
|
3045
3897
|
|
|
3046
|
-
const files = fs$
|
|
3898
|
+
const files = fs$b.readdirSync(source);
|
|
3047
3899
|
for (const file of files) {
|
|
3048
|
-
const srcPath = path$
|
|
3049
|
-
const destPath = path$
|
|
3050
|
-
const stat = fs$
|
|
3900
|
+
const srcPath = path$g.join(source, file);
|
|
3901
|
+
const destPath = path$g.join(destination, file);
|
|
3902
|
+
const stat = fs$b.lstatSync(srcPath);
|
|
3051
3903
|
|
|
3052
3904
|
// Skip symlinks to prevent following links to sensitive files
|
|
3053
3905
|
if (stat.isSymbolicLink()) {
|
|
@@ -3058,7 +3910,7 @@ function copyDirectory(source, destination) {
|
|
|
3058
3910
|
if (stat.isDirectory()) {
|
|
3059
3911
|
copyDirectory(srcPath, destPath);
|
|
3060
3912
|
} else {
|
|
3061
|
-
fs$
|
|
3913
|
+
fs$b.copyFileSync(srcPath, destPath);
|
|
3062
3914
|
}
|
|
3063
3915
|
}
|
|
3064
3916
|
}
|
|
@@ -3074,8 +3926,8 @@ const settingsController$4 = {
|
|
|
3074
3926
|
try {
|
|
3075
3927
|
if (data) {
|
|
3076
3928
|
// <appId>/settings.json
|
|
3077
|
-
const filename = path$
|
|
3078
|
-
app$
|
|
3929
|
+
const filename = path$g.join(
|
|
3930
|
+
app$9.getPath("userData"),
|
|
3079
3931
|
appName$4,
|
|
3080
3932
|
configFilename$3,
|
|
3081
3933
|
);
|
|
@@ -3110,8 +3962,8 @@ const settingsController$4 = {
|
|
|
3110
3962
|
getSettingsForApplication: (win) => {
|
|
3111
3963
|
try {
|
|
3112
3964
|
// <appId>/settings.json
|
|
3113
|
-
const filename = path$
|
|
3114
|
-
app$
|
|
3965
|
+
const filename = path$g.join(
|
|
3966
|
+
app$9.getPath("userData"),
|
|
3115
3967
|
appName$4,
|
|
3116
3968
|
configFilename$3,
|
|
3117
3969
|
);
|
|
@@ -3141,15 +3993,15 @@ const settingsController$4 = {
|
|
|
3141
3993
|
*/
|
|
3142
3994
|
getDataDirectory: (win) => {
|
|
3143
3995
|
try {
|
|
3144
|
-
const settingsPath = path$
|
|
3145
|
-
app$
|
|
3996
|
+
const settingsPath = path$g.join(
|
|
3997
|
+
app$9.getPath("userData"),
|
|
3146
3998
|
appName$4,
|
|
3147
3999
|
configFilename$3,
|
|
3148
4000
|
);
|
|
3149
4001
|
const settings = getFileContents$4(settingsPath, {});
|
|
3150
4002
|
const userDataDir =
|
|
3151
4003
|
settings.userDataDirectory ||
|
|
3152
|
-
path$
|
|
4004
|
+
path$g.join(app$9.getPath("userData"), appName$4);
|
|
3153
4005
|
|
|
3154
4006
|
console.log("[settingsController] Data directory retrieved successfully");
|
|
3155
4007
|
// Return the data for ipcMain.handle() - modern promise-based approach
|
|
@@ -3176,18 +4028,18 @@ const settingsController$4 = {
|
|
|
3176
4028
|
setDataDirectory: (win, newPath) => {
|
|
3177
4029
|
try {
|
|
3178
4030
|
// Validate the path exists and is a directory
|
|
3179
|
-
if (!fs$
|
|
3180
|
-
fs$
|
|
4031
|
+
if (!fs$b.existsSync(newPath)) {
|
|
4032
|
+
fs$b.mkdirSync(newPath, { recursive: true });
|
|
3181
4033
|
}
|
|
3182
4034
|
|
|
3183
|
-
const stats = fs$
|
|
4035
|
+
const stats = fs$b.statSync(newPath);
|
|
3184
4036
|
if (!stats.isDirectory()) {
|
|
3185
4037
|
throw new Error("Path is not a directory");
|
|
3186
4038
|
}
|
|
3187
4039
|
|
|
3188
4040
|
// Update settings
|
|
3189
|
-
const settingsPath = path$
|
|
3190
|
-
app$
|
|
4041
|
+
const settingsPath = path$g.join(
|
|
4042
|
+
app$9.getPath("userData"),
|
|
3191
4043
|
appName$4,
|
|
3192
4044
|
configFilename$3,
|
|
3193
4045
|
);
|
|
@@ -3220,20 +4072,20 @@ const settingsController$4 = {
|
|
|
3220
4072
|
migrateDataDirectory: (win, oldPath, newPath) => {
|
|
3221
4073
|
try {
|
|
3222
4074
|
// Resolve paths to prevent traversal
|
|
3223
|
-
const resolvedOldPath = path$
|
|
3224
|
-
const resolvedNewPath = path$
|
|
4075
|
+
const resolvedOldPath = path$g.resolve(oldPath);
|
|
4076
|
+
const resolvedNewPath = path$g.resolve(newPath);
|
|
3225
4077
|
|
|
3226
4078
|
// Validate oldPath is the current configured data directory
|
|
3227
|
-
const settingsCheckPath = path$
|
|
3228
|
-
app$
|
|
4079
|
+
const settingsCheckPath = path$g.join(
|
|
4080
|
+
app$9.getPath("userData"),
|
|
3229
4081
|
appName$4,
|
|
3230
4082
|
configFilename$3,
|
|
3231
4083
|
);
|
|
3232
4084
|
const currentSettings = getFileContents$4(settingsCheckPath, {});
|
|
3233
4085
|
const currentDataDir =
|
|
3234
4086
|
currentSettings.userDataDirectory ||
|
|
3235
|
-
path$
|
|
3236
|
-
if (resolvedOldPath !== path$
|
|
4087
|
+
path$g.join(app$9.getPath("userData"), appName$4);
|
|
4088
|
+
if (resolvedOldPath !== path$g.resolve(currentDataDir)) {
|
|
3237
4089
|
throw new Error("Source path must be the current data directory");
|
|
3238
4090
|
}
|
|
3239
4091
|
|
|
@@ -3257,20 +4109,20 @@ const settingsController$4 = {
|
|
|
3257
4109
|
}
|
|
3258
4110
|
|
|
3259
4111
|
// Validate paths
|
|
3260
|
-
if (!fs$
|
|
4112
|
+
if (!fs$b.existsSync(resolvedOldPath)) {
|
|
3261
4113
|
throw new Error("Source directory does not exist");
|
|
3262
4114
|
}
|
|
3263
4115
|
|
|
3264
|
-
if (!fs$
|
|
3265
|
-
fs$
|
|
4116
|
+
if (!fs$b.existsSync(resolvedNewPath)) {
|
|
4117
|
+
fs$b.mkdirSync(resolvedNewPath, { recursive: true });
|
|
3266
4118
|
}
|
|
3267
4119
|
|
|
3268
4120
|
// Copy files
|
|
3269
4121
|
copyDirectory(resolvedOldPath, resolvedNewPath);
|
|
3270
4122
|
|
|
3271
4123
|
// Update settings to use new path
|
|
3272
|
-
const settingsPath = path$
|
|
3273
|
-
app$
|
|
4124
|
+
const settingsPath = path$g.join(
|
|
4125
|
+
app$9.getPath("userData"),
|
|
3274
4126
|
appName$4,
|
|
3275
4127
|
configFilename$3,
|
|
3276
4128
|
);
|
|
@@ -3553,7 +4405,7 @@ function requireProviderController () {
|
|
|
3553
4405
|
if (hasRequiredProviderController) return providerController_1;
|
|
3554
4406
|
hasRequiredProviderController = 1;
|
|
3555
4407
|
const { app, safeStorage } = require$$0$1;
|
|
3556
|
-
const path = require$$1$
|
|
4408
|
+
const path = require$$1$1;
|
|
3557
4409
|
const { writeFileSync, readFileSync, existsSync } = require$$0$2;
|
|
3558
4410
|
const {
|
|
3559
4411
|
ensureDirectoryExistence,
|
|
@@ -3934,8 +4786,8 @@ function requireProviderController () {
|
|
|
3934
4786
|
return providerController_1;
|
|
3935
4787
|
}
|
|
3936
4788
|
|
|
3937
|
-
const { app: app$
|
|
3938
|
-
const path$
|
|
4789
|
+
const { app: app$8 } = require$$0$1;
|
|
4790
|
+
const path$f = require$$1$1;
|
|
3939
4791
|
const { writeFileSync: writeFileSync$1 } = require$$0$2;
|
|
3940
4792
|
const events$4 = events$8;
|
|
3941
4793
|
const { getFileContents: getFileContents$3 } = file;
|
|
@@ -3955,8 +4807,8 @@ const layoutController$1 = {
|
|
|
3955
4807
|
saveLayoutForApplication: (win, appId, layoutObject) => {
|
|
3956
4808
|
try {
|
|
3957
4809
|
// filename to the pages file (live pages)
|
|
3958
|
-
const filename = path$
|
|
3959
|
-
app$
|
|
4810
|
+
const filename = path$f.join(
|
|
4811
|
+
app$8.getPath("userData"),
|
|
3960
4812
|
appName$3,
|
|
3961
4813
|
appId,
|
|
3962
4814
|
configFilename$2,
|
|
@@ -3988,8 +4840,8 @@ const layoutController$1 = {
|
|
|
3988
4840
|
*/
|
|
3989
4841
|
listLayoutsForApplication: (win, appId) => {
|
|
3990
4842
|
try {
|
|
3991
|
-
const filename = path$
|
|
3992
|
-
app$
|
|
4843
|
+
const filename = path$f.join(
|
|
4844
|
+
app$8.getPath("userData"),
|
|
3993
4845
|
appName$3,
|
|
3994
4846
|
appId,
|
|
3995
4847
|
configFilename$2,
|
|
@@ -21152,455 +22004,6 @@ let StreamableHTTPClientTransport$1 = class StreamableHTTPClientTransport {
|
|
|
21152
22004
|
};
|
|
21153
22005
|
streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1;
|
|
21154
22006
|
|
|
21155
|
-
/**
|
|
21156
|
-
* grantedPermissions.js
|
|
21157
|
-
*
|
|
21158
|
-
* Stores the user's actual MCP permission grants per widget. This is the
|
|
21159
|
-
* Slice-2 enforcement source of truth — separate from the widget's declared
|
|
21160
|
-
* `dash.permissions.mcp` block (which is just a request).
|
|
21161
|
-
*
|
|
21162
|
-
* The runtime gate (permissionGate.gateToolCall) reads from here only.
|
|
21163
|
-
* A widget with a declared manifest but no grant entry has no access:
|
|
21164
|
-
* fail-closed. The user grants permissions at install time (consent modal)
|
|
21165
|
-
* or later in Settings → Privacy & Security.
|
|
21166
|
-
*
|
|
21167
|
-
* Storage: userData/widgetMcpGrants.json. Atomic writes via tmp + rename.
|
|
21168
|
-
*
|
|
21169
|
-
* Shape on disk:
|
|
21170
|
-
* {
|
|
21171
|
-
* "@trops/notes-summarizer": {
|
|
21172
|
-
* "servers": {
|
|
21173
|
-
* "filesystem": {
|
|
21174
|
-
* "tools": ["read_file"],
|
|
21175
|
-
* "readPaths": ["/Users/jane/Documents/notes"],
|
|
21176
|
-
* "writePaths": []
|
|
21177
|
-
* }
|
|
21178
|
-
* }
|
|
21179
|
-
* }
|
|
21180
|
-
* }
|
|
21181
|
-
*
|
|
21182
|
-
* Note: paths are stored as-is (already tilde-expanded by the manifest
|
|
21183
|
-
* parser before grants are written). Tests can re-expand via
|
|
21184
|
-
* widgetPermissions.expandHome if they store ~ literals.
|
|
21185
|
-
*
|
|
21186
|
-
* Public API:
|
|
21187
|
-
* getGrant(widgetId) → grant | null
|
|
21188
|
-
* setGrant(widgetId, perms) → boolean
|
|
21189
|
-
* revokeGrant(widgetId) → boolean
|
|
21190
|
-
* revokeServer(widgetId, serverName) → boolean
|
|
21191
|
-
* listAllGrants() → [{ widgetId, granted }]
|
|
21192
|
-
* clearCache() → void // test-only
|
|
21193
|
-
*/
|
|
21194
|
-
|
|
21195
|
-
const fs$b = require$$0$2;
|
|
21196
|
-
const path$f = require$$1$2;
|
|
21197
|
-
const { app: app$8 } = require$$0$1;
|
|
21198
|
-
|
|
21199
|
-
const FILE_NAME = "widgetMcpGrants.json";
|
|
21200
|
-
|
|
21201
|
-
// In-process cache of the entire grants file. Lazily loaded; invalidated
|
|
21202
|
-
// on every write.
|
|
21203
|
-
let _cache$1 = null;
|
|
21204
|
-
|
|
21205
|
-
function grantsFilePath() {
|
|
21206
|
-
return path$f.join(app$8.getPath("userData"), FILE_NAME);
|
|
21207
|
-
}
|
|
21208
|
-
|
|
21209
|
-
function loadFromDisk() {
|
|
21210
|
-
const p = grantsFilePath();
|
|
21211
|
-
if (!fs$b.existsSync(p)) return {};
|
|
21212
|
-
try {
|
|
21213
|
-
const raw = fs$b.readFileSync(p, "utf8");
|
|
21214
|
-
const parsed = JSON.parse(raw);
|
|
21215
|
-
if (!parsed || typeof parsed !== "object") return {};
|
|
21216
|
-
return parsed;
|
|
21217
|
-
} catch (e) {
|
|
21218
|
-
console.warn("[grantedPermissions] failed to read " + p + ": " + e.message);
|
|
21219
|
-
return {};
|
|
21220
|
-
}
|
|
21221
|
-
}
|
|
21222
|
-
|
|
21223
|
-
function ensureCache() {
|
|
21224
|
-
if (_cache$1 === null) _cache$1 = loadFromDisk();
|
|
21225
|
-
return _cache$1;
|
|
21226
|
-
}
|
|
21227
|
-
|
|
21228
|
-
function writeToDisk(data) {
|
|
21229
|
-
const p = grantsFilePath();
|
|
21230
|
-
const tmp = p + ".tmp";
|
|
21231
|
-
// Ensure parent dir exists (userData should already, but be defensive
|
|
21232
|
-
// for first-launch / freshly-cleared profile cases).
|
|
21233
|
-
fs$b.mkdirSync(path$f.dirname(p), { recursive: true });
|
|
21234
|
-
fs$b.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
|
|
21235
|
-
fs$b.renameSync(tmp, p);
|
|
21236
|
-
}
|
|
21237
|
-
|
|
21238
|
-
// Recognized origins for a persisted grant.
|
|
21239
|
-
// "declared" — user approved against the developer's declared
|
|
21240
|
-
// dash.permissions.mcp block at install time.
|
|
21241
|
-
// "discovered" — install-time scanner produced a synthetic manifest
|
|
21242
|
-
// the user approved.
|
|
21243
|
-
// "manual" — user typed entries themselves in
|
|
21244
|
-
// Settings → Privacy & Security with no manifest backing.
|
|
21245
|
-
// "live" — user approved a just-in-time consent prompt at
|
|
21246
|
-
// runtime when a tool call hit the gate without a
|
|
21247
|
-
// matching grant.
|
|
21248
|
-
// Other values are dropped on persist (legacy grants stay null).
|
|
21249
|
-
const ALLOWED_GRANT_ORIGINS = new Set([
|
|
21250
|
-
"declared",
|
|
21251
|
-
"discovered",
|
|
21252
|
-
"manual",
|
|
21253
|
-
"live",
|
|
21254
|
-
]);
|
|
21255
|
-
|
|
21256
|
-
/**
|
|
21257
|
-
* Sanitize a perms object before persisting. Drops unknown keys, coerces
|
|
21258
|
-
* arrays of strings, and silently ignores malformed servers. Mirrors the
|
|
21259
|
-
* shape produced by parseManifestPermissions so the gate reads either
|
|
21260
|
-
* declared or granted with the same code path.
|
|
21261
|
-
*
|
|
21262
|
-
* Optional `grantOrigin` field is preserved when it's one of the
|
|
21263
|
-
* recognized values; bogus values are dropped.
|
|
21264
|
-
*/
|
|
21265
|
-
function sanitizePerms(perms) {
|
|
21266
|
-
if (!perms || typeof perms !== "object") return null;
|
|
21267
|
-
const rawServers =
|
|
21268
|
-
perms.servers && typeof perms.servers === "object" ? perms.servers : null;
|
|
21269
|
-
if (!rawServers) return null;
|
|
21270
|
-
|
|
21271
|
-
const servers = {};
|
|
21272
|
-
for (const [name, raw] of Object.entries(rawServers)) {
|
|
21273
|
-
if (!raw || typeof raw !== "object") continue;
|
|
21274
|
-
servers[name] = {
|
|
21275
|
-
tools: Array.isArray(raw.tools)
|
|
21276
|
-
? raw.tools.filter((t) => typeof t === "string")
|
|
21277
|
-
: [],
|
|
21278
|
-
readPaths: Array.isArray(raw.readPaths)
|
|
21279
|
-
? raw.readPaths.filter((p) => typeof p === "string")
|
|
21280
|
-
: [],
|
|
21281
|
-
writePaths: Array.isArray(raw.writePaths)
|
|
21282
|
-
? raw.writePaths.filter((p) => typeof p === "string")
|
|
21283
|
-
: [],
|
|
21284
|
-
};
|
|
21285
|
-
}
|
|
21286
|
-
const out = { servers };
|
|
21287
|
-
if (
|
|
21288
|
-
typeof perms.grantOrigin === "string" &&
|
|
21289
|
-
ALLOWED_GRANT_ORIGINS.has(perms.grantOrigin)
|
|
21290
|
-
) {
|
|
21291
|
-
out.grantOrigin = perms.grantOrigin;
|
|
21292
|
-
}
|
|
21293
|
-
return out;
|
|
21294
|
-
}
|
|
21295
|
-
|
|
21296
|
-
function getGrant$2(widgetId) {
|
|
21297
|
-
if (typeof widgetId !== "string" || !widgetId) return null;
|
|
21298
|
-
const all = ensureCache();
|
|
21299
|
-
return all[widgetId] || null;
|
|
21300
|
-
}
|
|
21301
|
-
|
|
21302
|
-
function setGrant$2(widgetId, perms) {
|
|
21303
|
-
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21304
|
-
const sanitized = sanitizePerms(perms);
|
|
21305
|
-
if (!sanitized) return false;
|
|
21306
|
-
const all = ensureCache();
|
|
21307
|
-
all[widgetId] = sanitized;
|
|
21308
|
-
try {
|
|
21309
|
-
writeToDisk(all);
|
|
21310
|
-
return true;
|
|
21311
|
-
} catch (e) {
|
|
21312
|
-
console.warn(
|
|
21313
|
-
"[grantedPermissions] failed to write grant for " +
|
|
21314
|
-
widgetId +
|
|
21315
|
-
": " +
|
|
21316
|
-
e.message,
|
|
21317
|
-
);
|
|
21318
|
-
// Roll back the cache entry so memory matches disk.
|
|
21319
|
-
_cache$1 = loadFromDisk();
|
|
21320
|
-
return false;
|
|
21321
|
-
}
|
|
21322
|
-
}
|
|
21323
|
-
|
|
21324
|
-
function revokeGrant$1(widgetId) {
|
|
21325
|
-
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21326
|
-
const all = ensureCache();
|
|
21327
|
-
if (!Object.prototype.hasOwnProperty.call(all, widgetId)) return false;
|
|
21328
|
-
delete all[widgetId];
|
|
21329
|
-
try {
|
|
21330
|
-
writeToDisk(all);
|
|
21331
|
-
return true;
|
|
21332
|
-
} catch (e) {
|
|
21333
|
-
console.warn(
|
|
21334
|
-
"[grantedPermissions] failed to revoke grant for " +
|
|
21335
|
-
widgetId +
|
|
21336
|
-
": " +
|
|
21337
|
-
e.message,
|
|
21338
|
-
);
|
|
21339
|
-
_cache$1 = loadFromDisk();
|
|
21340
|
-
return false;
|
|
21341
|
-
}
|
|
21342
|
-
}
|
|
21343
|
-
|
|
21344
|
-
function revokeServer$1(widgetId, serverName) {
|
|
21345
|
-
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21346
|
-
if (typeof serverName !== "string" || !serverName) return false;
|
|
21347
|
-
const all = ensureCache();
|
|
21348
|
-
const widgetEntry = all[widgetId];
|
|
21349
|
-
if (!widgetEntry || !widgetEntry.servers) return false;
|
|
21350
|
-
if (!Object.prototype.hasOwnProperty.call(widgetEntry.servers, serverName))
|
|
21351
|
-
return false;
|
|
21352
|
-
delete widgetEntry.servers[serverName];
|
|
21353
|
-
try {
|
|
21354
|
-
writeToDisk(all);
|
|
21355
|
-
return true;
|
|
21356
|
-
} catch (e) {
|
|
21357
|
-
console.warn(
|
|
21358
|
-
"[grantedPermissions] failed to revoke server " +
|
|
21359
|
-
serverName +
|
|
21360
|
-
" for " +
|
|
21361
|
-
widgetId +
|
|
21362
|
-
": " +
|
|
21363
|
-
e.message,
|
|
21364
|
-
);
|
|
21365
|
-
_cache$1 = loadFromDisk();
|
|
21366
|
-
return false;
|
|
21367
|
-
}
|
|
21368
|
-
}
|
|
21369
|
-
|
|
21370
|
-
function listAllGrants$1() {
|
|
21371
|
-
const all = ensureCache();
|
|
21372
|
-
return Object.entries(all).map(([widgetId, granted]) => ({
|
|
21373
|
-
widgetId,
|
|
21374
|
-
granted,
|
|
21375
|
-
}));
|
|
21376
|
-
}
|
|
21377
|
-
|
|
21378
|
-
function clearCache$1() {
|
|
21379
|
-
_cache$1 = null;
|
|
21380
|
-
}
|
|
21381
|
-
|
|
21382
|
-
var grantedPermissions = {
|
|
21383
|
-
getGrant: getGrant$2,
|
|
21384
|
-
setGrant: setGrant$2,
|
|
21385
|
-
revokeGrant: revokeGrant$1,
|
|
21386
|
-
revokeServer: revokeServer$1,
|
|
21387
|
-
listAllGrants: listAllGrants$1,
|
|
21388
|
-
clearCache: clearCache$1,
|
|
21389
|
-
ALLOWED_GRANT_ORIGINS,
|
|
21390
|
-
};
|
|
21391
|
-
|
|
21392
|
-
/**
|
|
21393
|
-
* jitConsent.js
|
|
21394
|
-
*
|
|
21395
|
-
* Just-in-time permission consent for widget→backend calls.
|
|
21396
|
-
*
|
|
21397
|
-
* When a widget hits a gate without an existing grant for the requested
|
|
21398
|
-
* (domain, action, args), the gate calls `requestApproval` which:
|
|
21399
|
-
* 1. Synchronously emits `widget:permission-required` to all
|
|
21400
|
-
* BrowserWindows with a unique requestId.
|
|
21401
|
-
* 2. Returns a Promise that resolves on user response or rejects on
|
|
21402
|
-
* timeout.
|
|
21403
|
-
* 3. Coalesces requests with the same coalescing key so a widget
|
|
21404
|
-
* bursting identical calls produces one prompt, not many.
|
|
21405
|
-
*
|
|
21406
|
-
* The renderer's JitConsentModal subscribes to the event, presents the
|
|
21407
|
-
* user with granularity options (this once / this tool / this tool +
|
|
21408
|
-
* parent dir), and replies via `widget:permission-response` with
|
|
21409
|
-
* `{ requestId, decision }`. main.js wires the IPC handler back to
|
|
21410
|
-
* `_handleResponse`.
|
|
21411
|
-
*
|
|
21412
|
-
* The module is intentionally domain-agnostic in shape — the request
|
|
21413
|
-
* payload carries `domain` so future plug-ins (fs, algolia, llm) reuse
|
|
21414
|
-
* the same machinery. Phase 1 only emits with `domain: "mcp"`.
|
|
21415
|
-
*
|
|
21416
|
-
* Public surface:
|
|
21417
|
-
* requestApproval(req, opts) → Promise<{ approve, scope?, ... }>
|
|
21418
|
-
* _handleResponse({ requestId, decision }) → void (called from main.js IPC)
|
|
21419
|
-
* _resetForTest() → void (test-only)
|
|
21420
|
-
*/
|
|
21421
|
-
|
|
21422
|
-
const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
|
|
21423
|
-
|
|
21424
|
-
const REQUEST_CHANNEL = "widget:permission-required";
|
|
21425
|
-
const RESPONSE_CHANNEL = "widget:permission-response";
|
|
21426
|
-
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
21427
|
-
|
|
21428
|
-
// requestId → { resolve, reject, timeout, coalesceKey, joinedResolvers }
|
|
21429
|
-
const _pending = new Map();
|
|
21430
|
-
// coalesceKey → requestId (so duplicate requests join the live one)
|
|
21431
|
-
const _coalesce = new Map();
|
|
21432
|
-
let _idCounter = 0;
|
|
21433
|
-
|
|
21434
|
-
function nextRequestId() {
|
|
21435
|
-
_idCounter += 1;
|
|
21436
|
-
return `jit-${Date.now()}-${_idCounter}`;
|
|
21437
|
-
}
|
|
21438
|
-
|
|
21439
|
-
/**
|
|
21440
|
-
* Build a coalescing key from the request. Two requests share the same
|
|
21441
|
-
* key iff they're "the same prompt" — same widget, same domain+action,
|
|
21442
|
-
* same target server/tool. Args beyond that (e.g. exact path) DON'T
|
|
21443
|
-
* differentiate; if the user is being asked about read_file already,
|
|
21444
|
-
* approving handles all current paths.
|
|
21445
|
-
*/
|
|
21446
|
-
function coalesceKeyOf(req) {
|
|
21447
|
-
if (req.domain === "mcp") {
|
|
21448
|
-
const innerArgs = req.args || {};
|
|
21449
|
-
return [
|
|
21450
|
-
req.widgetId,
|
|
21451
|
-
"mcp",
|
|
21452
|
-
innerArgs.serverName || "",
|
|
21453
|
-
innerArgs.toolName || "",
|
|
21454
|
-
].join("::");
|
|
21455
|
-
}
|
|
21456
|
-
// Default: domain + action + serialized top-level args
|
|
21457
|
-
return [
|
|
21458
|
-
req.widgetId,
|
|
21459
|
-
req.domain,
|
|
21460
|
-
req.action,
|
|
21461
|
-
JSON.stringify(req.args || {}),
|
|
21462
|
-
].join("::");
|
|
21463
|
-
}
|
|
21464
|
-
|
|
21465
|
-
function emitEvent(payload) {
|
|
21466
|
-
let wins = [];
|
|
21467
|
-
try {
|
|
21468
|
-
wins = BrowserWindow$2.getAllWindows() || [];
|
|
21469
|
-
} catch {
|
|
21470
|
-
wins = [];
|
|
21471
|
-
}
|
|
21472
|
-
for (const w of wins) {
|
|
21473
|
-
try {
|
|
21474
|
-
w?.webContents?.send?.(REQUEST_CHANNEL, payload);
|
|
21475
|
-
} catch {
|
|
21476
|
-
// best-effort broadcast
|
|
21477
|
-
}
|
|
21478
|
-
}
|
|
21479
|
-
}
|
|
21480
|
-
|
|
21481
|
-
function validateRequest(req) {
|
|
21482
|
-
if (!req || typeof req !== "object") return "invalid request: not an object";
|
|
21483
|
-
if (typeof req.widgetId !== "string" || !req.widgetId)
|
|
21484
|
-
return "invalid request: widgetId required";
|
|
21485
|
-
if (typeof req.domain !== "string" || !req.domain)
|
|
21486
|
-
return "invalid request: domain required";
|
|
21487
|
-
if (typeof req.action !== "string" || !req.action)
|
|
21488
|
-
return "invalid request: action required";
|
|
21489
|
-
return null;
|
|
21490
|
-
}
|
|
21491
|
-
|
|
21492
|
-
/**
|
|
21493
|
-
* Request user approval for an out-of-grant call. Returns a promise
|
|
21494
|
-
* that resolves with the user's decision or rejects on timeout / bad
|
|
21495
|
-
* input.
|
|
21496
|
-
*
|
|
21497
|
-
* decision shape (resolved value):
|
|
21498
|
-
* { approve: true, scope: "once" | "tool" | "parent" | "custom", ...extras }
|
|
21499
|
-
* { approve: false, reason?: string }
|
|
21500
|
-
*
|
|
21501
|
-
* `scope` informs the caller how to write the resulting grant.
|
|
21502
|
-
*/
|
|
21503
|
-
function requestApproval$1(req, opts = {}) {
|
|
21504
|
-
const validation = validateRequest(req);
|
|
21505
|
-
if (validation) {
|
|
21506
|
-
return Promise.reject(new Error(validation));
|
|
21507
|
-
}
|
|
21508
|
-
|
|
21509
|
-
const timeoutMs = Number.isFinite(opts.timeoutMs)
|
|
21510
|
-
? opts.timeoutMs
|
|
21511
|
-
: DEFAULT_TIMEOUT_MS;
|
|
21512
|
-
|
|
21513
|
-
// If a prompt for the same coalesce key is already pending, join it.
|
|
21514
|
-
const key = coalesceKeyOf(req);
|
|
21515
|
-
if (_coalesce.has(key)) {
|
|
21516
|
-
const existingId = _coalesce.get(key);
|
|
21517
|
-
const existing = _pending.get(existingId);
|
|
21518
|
-
if (existing) {
|
|
21519
|
-
return new Promise((resolve, reject) => {
|
|
21520
|
-
existing.joinedResolvers.push({ resolve, reject });
|
|
21521
|
-
});
|
|
21522
|
-
}
|
|
21523
|
-
// Stale coalesce entry; drop and fall through to a fresh request.
|
|
21524
|
-
_coalesce.delete(key);
|
|
21525
|
-
}
|
|
21526
|
-
|
|
21527
|
-
return new Promise((resolve, reject) => {
|
|
21528
|
-
const requestId = nextRequestId();
|
|
21529
|
-
const timeout = setTimeout(() => {
|
|
21530
|
-
const entry = _pending.get(requestId);
|
|
21531
|
-
if (!entry) return;
|
|
21532
|
-
_pending.delete(requestId);
|
|
21533
|
-
_coalesce.delete(entry.coalesceKey);
|
|
21534
|
-
const err = new Error(
|
|
21535
|
-
`JIT consent timed out for ${req.widgetId} (${req.domain}/${req.action}) after ${timeoutMs}ms`,
|
|
21536
|
-
);
|
|
21537
|
-
reject(err);
|
|
21538
|
-
for (const j of entry.joinedResolvers) j.reject(err);
|
|
21539
|
-
}, timeoutMs);
|
|
21540
|
-
|
|
21541
|
-
_pending.set(requestId, {
|
|
21542
|
-
resolve,
|
|
21543
|
-
reject,
|
|
21544
|
-
timeout,
|
|
21545
|
-
coalesceKey: key,
|
|
21546
|
-
joinedResolvers: [],
|
|
21547
|
-
});
|
|
21548
|
-
_coalesce.set(key, requestId);
|
|
21549
|
-
|
|
21550
|
-
emitEvent({
|
|
21551
|
-
requestId,
|
|
21552
|
-
widgetId: req.widgetId,
|
|
21553
|
-
domain: req.domain,
|
|
21554
|
-
action: req.action,
|
|
21555
|
-
args: req.args || {},
|
|
21556
|
-
});
|
|
21557
|
-
});
|
|
21558
|
-
}
|
|
21559
|
-
|
|
21560
|
-
function _handleResponse({ requestId, decision } = {}) {
|
|
21561
|
-
if (!requestId || typeof requestId !== "string") return;
|
|
21562
|
-
const entry = _pending.get(requestId);
|
|
21563
|
-
if (!entry) return; // unknown request — drop silently
|
|
21564
|
-
clearTimeout(entry.timeout);
|
|
21565
|
-
_pending.delete(requestId);
|
|
21566
|
-
_coalesce.delete(entry.coalesceKey);
|
|
21567
|
-
const safe =
|
|
21568
|
-
decision && typeof decision === "object" ? decision : { approve: false };
|
|
21569
|
-
entry.resolve(safe);
|
|
21570
|
-
for (const j of entry.joinedResolvers) j.resolve(safe);
|
|
21571
|
-
}
|
|
21572
|
-
|
|
21573
|
-
function _resetForTest() {
|
|
21574
|
-
for (const entry of _pending.values()) clearTimeout(entry.timeout);
|
|
21575
|
-
_pending.clear();
|
|
21576
|
-
_coalesce.clear();
|
|
21577
|
-
_idCounter = 0;
|
|
21578
|
-
}
|
|
21579
|
-
|
|
21580
|
-
let _handlersRegistered = false;
|
|
21581
|
-
/**
|
|
21582
|
-
* Wire the renderer→main response IPC. Idempotent.
|
|
21583
|
-
* Call once from main.js alongside other ipcMain setup.
|
|
21584
|
-
*/
|
|
21585
|
-
function setupJitConsentHandlers() {
|
|
21586
|
-
if (_handlersRegistered) return;
|
|
21587
|
-
if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
|
|
21588
|
-
ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
|
|
21589
|
-
_handleResponse(payload);
|
|
21590
|
-
});
|
|
21591
|
-
_handlersRegistered = true;
|
|
21592
|
-
}
|
|
21593
|
-
|
|
21594
|
-
var jitConsent$1 = {
|
|
21595
|
-
requestApproval: requestApproval$1,
|
|
21596
|
-
setupJitConsentHandlers,
|
|
21597
|
-
_handleResponse,
|
|
21598
|
-
_resetForTest,
|
|
21599
|
-
REQUEST_CHANNEL,
|
|
21600
|
-
RESPONSE_CHANNEL,
|
|
21601
|
-
DEFAULT_TIMEOUT_MS,
|
|
21602
|
-
};
|
|
21603
|
-
|
|
21604
22007
|
/**
|
|
21605
22008
|
* permissionGate.js
|
|
21606
22009
|
*
|
|
@@ -22081,37 +22484,6 @@ var mcpScopeResolver = {
|
|
|
22081
22484
|
applyPathScopeToCredentials: applyPathScopeToCredentials$1,
|
|
22082
22485
|
};
|
|
22083
22486
|
|
|
22084
|
-
/**
|
|
22085
|
-
* securityFlags.js
|
|
22086
|
-
*
|
|
22087
|
-
* Centralized readers for the two boolean security flags that gate the
|
|
22088
|
-
* MCP allowlist stack:
|
|
22089
|
-
* - security.enforceWidgetMcpPermissions
|
|
22090
|
-
* - security.enableJitConsent
|
|
22091
|
-
*
|
|
22092
|
-
* **Default semantics: ON.** A missing settings.json, a missing
|
|
22093
|
-
* `security` block, or an undefined field all yield `true`. Only an
|
|
22094
|
-
* explicit `false` opts out. This is intentional — the security stack
|
|
22095
|
-
* is on by default; users have to actively disable it. The
|
|
22096
|
-
* Privacy & Security panel surfaces the toggles + a confirm-on-disable
|
|
22097
|
-
* dialog so the disable path is deliberate.
|
|
22098
|
-
*
|
|
22099
|
-
* The readers are pure functions of a settings object so the
|
|
22100
|
-
* default-on semantics are pinned by unit tests without touching the
|
|
22101
|
-
* filesystem. The callers in mcpController.js wrap these with
|
|
22102
|
-
* settings.json IO.
|
|
22103
|
-
*/
|
|
22104
|
-
|
|
22105
|
-
function readEnforceFlag$1(settings) {
|
|
22106
|
-
return settings?.security?.enforceWidgetMcpPermissions !== false;
|
|
22107
|
-
}
|
|
22108
|
-
|
|
22109
|
-
function readJitFlag$1(settings) {
|
|
22110
|
-
return settings?.security?.enableJitConsent !== false;
|
|
22111
|
-
}
|
|
22112
|
-
|
|
22113
|
-
var securityFlags = { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFlag$1 };
|
|
22114
|
-
|
|
22115
22487
|
/**
|
|
22116
22488
|
* mcpController.js
|
|
22117
22489
|
*
|
|
@@ -22128,11 +22500,11 @@ var securityFlags = { readEnforceFlag: readEnforceFlag$1, readJitFlag: readJitFl
|
|
|
22128
22500
|
const { Client } = require$$0$5;
|
|
22129
22501
|
const {
|
|
22130
22502
|
StdioClientTransport,
|
|
22131
|
-
} = require$$1$
|
|
22503
|
+
} = require$$1$3;
|
|
22132
22504
|
const {
|
|
22133
22505
|
StreamableHTTPClientTransport,
|
|
22134
22506
|
} = streamableHttp$1;
|
|
22135
|
-
const path$e = require$$1$
|
|
22507
|
+
const path$e = require$$1$1;
|
|
22136
22508
|
const fs$a = require$$0$2;
|
|
22137
22509
|
const os$2 = require$$2$1;
|
|
22138
22510
|
const responseCache$2 = responseCache_1;
|
|
@@ -22491,7 +22863,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
|
|
|
22491
22863
|
|
|
22492
22864
|
console.log("[mcpController] Refreshing Google OAuth token...");
|
|
22493
22865
|
|
|
22494
|
-
const https = require$$
|
|
22866
|
+
const https = require$$10;
|
|
22495
22867
|
const postData = [
|
|
22496
22868
|
`client_id=${encodeURIComponent(keyData.client_id)}`,
|
|
22497
22869
|
`client_secret=${encodeURIComponent(keyData.client_secret)}`,
|
|
@@ -23288,7 +23660,7 @@ const mcpController$3 = {
|
|
|
23288
23660
|
const sourcePath = credentials?.[from];
|
|
23289
23661
|
if (sourcePath) {
|
|
23290
23662
|
const destPath = to.replace(/^~/, os$2.homedir());
|
|
23291
|
-
const destDir = require$$1$
|
|
23663
|
+
const destDir = require$$1$1.dirname(destPath);
|
|
23292
23664
|
try {
|
|
23293
23665
|
fs$a.mkdirSync(destDir, { recursive: true });
|
|
23294
23666
|
fs$a.copyFileSync(sourcePath, destPath);
|
|
@@ -23508,7 +23880,7 @@ const REGISTRY_BASE_URL$1 =
|
|
|
23508
23880
|
let store$3 = null;
|
|
23509
23881
|
function getStore$1() {
|
|
23510
23882
|
if (!store$3) {
|
|
23511
|
-
const Store = require$$
|
|
23883
|
+
const Store = require$$0$6;
|
|
23512
23884
|
store$3 = new Store({
|
|
23513
23885
|
name: "dash-registry-auth",
|
|
23514
23886
|
encryptionKey: "dash-registry-v1",
|
|
@@ -23876,7 +24248,7 @@ function commonjsRequire(path) {
|
|
|
23876
24248
|
*/
|
|
23877
24249
|
|
|
23878
24250
|
const fs$9 = require$$0$2;
|
|
23879
|
-
const path$d = require$$1$
|
|
24251
|
+
const path$d = require$$1$1;
|
|
23880
24252
|
|
|
23881
24253
|
/**
|
|
23882
24254
|
* Structured error thrown by compileWidget() when the underlying
|
|
@@ -24193,7 +24565,7 @@ var widgetCompiler$1 = {
|
|
|
24193
24565
|
*/
|
|
24194
24566
|
|
|
24195
24567
|
const fs$8 = require$$0$2;
|
|
24196
|
-
const path$c = require$$1$
|
|
24568
|
+
const path$c = require$$1$1;
|
|
24197
24569
|
const vm = require$$2$2;
|
|
24198
24570
|
const { findWidgetsDir: findWidgetsDir$1 } = widgetCompiler$1;
|
|
24199
24571
|
|
|
@@ -24484,7 +24856,7 @@ var dynamicWidgetLoaderExports = dynamicWidgetLoader$3.exports;
|
|
|
24484
24856
|
*/
|
|
24485
24857
|
|
|
24486
24858
|
const fs$7 = require$$0$2;
|
|
24487
|
-
const path$b = require$$1$
|
|
24859
|
+
const path$b = require$$1$1;
|
|
24488
24860
|
const os$1 = require$$2$1;
|
|
24489
24861
|
const { app: app$6 } = require$$0$1;
|
|
24490
24862
|
|
|
@@ -24613,7 +24985,7 @@ var widgetPermissions = {
|
|
|
24613
24985
|
*/
|
|
24614
24986
|
|
|
24615
24987
|
const fs$6 = require$$0$2;
|
|
24616
|
-
const path$a = require$$1$
|
|
24988
|
+
const path$a = require$$1$1;
|
|
24617
24989
|
|
|
24618
24990
|
const SOURCE_EXTENSIONS = new Set([
|
|
24619
24991
|
".js",
|
|
@@ -24793,8 +25165,8 @@ var manifestScanner = {
|
|
|
24793
25165
|
* and dispatching task-fired events to renderer windows.
|
|
24794
25166
|
*/
|
|
24795
25167
|
|
|
24796
|
-
const Store$1 = require$$
|
|
24797
|
-
const { Cron } = require$$1$
|
|
25168
|
+
const Store$1 = require$$0$6;
|
|
25169
|
+
const { Cron } = require$$1$4;
|
|
24798
25170
|
|
|
24799
25171
|
const store$2 = new Store$1({ name: "dash-scheduler" });
|
|
24800
25172
|
|
|
@@ -25316,7 +25688,7 @@ var schedulerController_1 = schedulerController$2;
|
|
|
25316
25688
|
|
|
25317
25689
|
(function (module) {
|
|
25318
25690
|
const fs = require$$0$2;
|
|
25319
|
-
const path = require$$1$
|
|
25691
|
+
const path = require$$1$1;
|
|
25320
25692
|
const os = require$$2$1;
|
|
25321
25693
|
const AdmZip = require$$3$2;
|
|
25322
25694
|
const { fileURLToPath } = require$$4$1;
|
|
@@ -26839,7 +27211,7 @@ var widgetRegistryExports = widgetRegistry$1.exports;
|
|
|
26839
27211
|
* - Support two-level browsing: packages (bundles) and widgets within packages
|
|
26840
27212
|
*/
|
|
26841
27213
|
|
|
26842
|
-
const path$9 = require$$1$
|
|
27214
|
+
const path$9 = require$$1$1;
|
|
26843
27215
|
const fs$5 = require$$0$2;
|
|
26844
27216
|
const os = require$$2$1;
|
|
26845
27217
|
const { toPackageId } = packageId;
|
|
@@ -28009,7 +28381,7 @@ const algoliaController$1 = {
|
|
|
28009
28381
|
|
|
28010
28382
|
var algoliaController_1 = algoliaController$1;
|
|
28011
28383
|
|
|
28012
|
-
const OpenAI = require$$0$
|
|
28384
|
+
const OpenAI = require$$0$7;
|
|
28013
28385
|
const events$2 = events$8;
|
|
28014
28386
|
|
|
28015
28387
|
const openaiController$1 = {
|
|
@@ -28113,7 +28485,7 @@ function upsertMenuItem$1(items, menuItem) {
|
|
|
28113
28485
|
var upsertMenuItem_1 = { upsertMenuItem: upsertMenuItem$1 };
|
|
28114
28486
|
|
|
28115
28487
|
const { app: app$5 } = require$$0$1;
|
|
28116
|
-
const path$7 = require$$1$
|
|
28488
|
+
const path$7 = require$$1$1;
|
|
28117
28489
|
const { writeFileSync } = require$$0$2;
|
|
28118
28490
|
const { getFileContents: getFileContents$2 } = file;
|
|
28119
28491
|
const { upsertMenuItem } = upsertMenuItem_1;
|
|
@@ -28169,7 +28541,7 @@ const menuItemsController$1 = {
|
|
|
28169
28541
|
|
|
28170
28542
|
var menuItemsController_1 = menuItemsController$1;
|
|
28171
28543
|
|
|
28172
|
-
const path$6 = require$$1$
|
|
28544
|
+
const path$6 = require$$1$1;
|
|
28173
28545
|
const { app: app$4 } = require$$0$1;
|
|
28174
28546
|
|
|
28175
28547
|
const pluginController$1 = {
|
|
@@ -46388,13 +46760,13 @@ __export(src_exports, {
|
|
|
46388
46760
|
var dist = __toCommonJS(src_exports);
|
|
46389
46761
|
|
|
46390
46762
|
// src/server.ts
|
|
46391
|
-
var import_node_http = require$$0$
|
|
46763
|
+
var import_node_http = require$$0$8;
|
|
46392
46764
|
|
|
46393
46765
|
// src/listener.ts
|
|
46394
|
-
var import_node_http22 = require$$1$
|
|
46766
|
+
var import_node_http22 = require$$1$5;
|
|
46395
46767
|
|
|
46396
46768
|
// src/request.ts
|
|
46397
|
-
var import_node_http2 = require$$1$
|
|
46769
|
+
var import_node_http2 = require$$1$5;
|
|
46398
46770
|
var import_node_stream = require$$5;
|
|
46399
46771
|
var RequestError = class extends Error {
|
|
46400
46772
|
constructor(message, options) {
|
|
@@ -47972,7 +48344,7 @@ streamableHttp.StreamableHTTPServerTransport = StreamableHTTPServerTransport$1;
|
|
|
47972
48344
|
*/
|
|
47973
48345
|
|
|
47974
48346
|
const fs$2 = require$$0$2;
|
|
47975
|
-
const path$5 = require$$1$
|
|
48347
|
+
const path$5 = require$$1$1;
|
|
47976
48348
|
const forge = require$$2$4;
|
|
47977
48349
|
|
|
47978
48350
|
/**
|
|
@@ -48166,7 +48538,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
|
|
|
48166
48538
|
* - Rate limiting via token bucket (60 req/min)
|
|
48167
48539
|
*/
|
|
48168
48540
|
|
|
48169
|
-
const https$1 = require$$
|
|
48541
|
+
const https$1 = require$$10;
|
|
48170
48542
|
const { randomUUID } = require$$3$4;
|
|
48171
48543
|
const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
|
|
48172
48544
|
const { McpServer } = mcp;
|
|
@@ -48371,7 +48743,7 @@ function saveMcpServerSettings(win, mcpSettings) {
|
|
|
48371
48743
|
function resolveAppId() {
|
|
48372
48744
|
const { app } = require$$0$1;
|
|
48373
48745
|
const fs = require$$0$2;
|
|
48374
|
-
const path = require$$1$
|
|
48746
|
+
const path = require$$1$1;
|
|
48375
48747
|
const dashboardDir = path.join(app.getPath("userData"), "Dashboard");
|
|
48376
48748
|
try {
|
|
48377
48749
|
const entries = fs.readdirSync(dashboardDir, { withFileTypes: true });
|
|
@@ -49973,10 +50345,10 @@ var themeFromUrlErrors$1 = {
|
|
|
49973
50345
|
* computed styles, and favicon/logo images (via node-vibrant).
|
|
49974
50346
|
*/
|
|
49975
50347
|
|
|
49976
|
-
const css = require$$0$
|
|
49977
|
-
const { Vibrant } = require$$1$
|
|
49978
|
-
const https = require$$
|
|
49979
|
-
const http = require$$0$
|
|
50348
|
+
const css = require$$0$9;
|
|
50349
|
+
const { Vibrant } = require$$1$6;
|
|
50350
|
+
const https = require$$10;
|
|
50351
|
+
const http = require$$0$8;
|
|
49980
50352
|
const { URL: URL$1 } = require$$4$1;
|
|
49981
50353
|
const {
|
|
49982
50354
|
UrlUnreachableError,
|
|
@@ -54536,7 +54908,7 @@ var toolHandlers$1 = {
|
|
|
54536
54908
|
* per-request, receiving the full messages array each time.
|
|
54537
54909
|
*/
|
|
54538
54910
|
|
|
54539
|
-
const Anthropic = require$$0$
|
|
54911
|
+
const Anthropic = require$$0$a;
|
|
54540
54912
|
const mcpController$2 = mcpControllerExports;
|
|
54541
54913
|
const cliController$1 = cliController_1;
|
|
54542
54914
|
const toolDefinitions = toolDefinitions$1;
|
|
@@ -56717,7 +57089,7 @@ var dashboardConfigUtils$1 = {
|
|
|
56717
57089
|
*/
|
|
56718
57090
|
|
|
56719
57091
|
const fs$1 = require$$0$2;
|
|
56720
|
-
const path$4 = require$$1$
|
|
57092
|
+
const path$4 = require$$1$1;
|
|
56721
57093
|
const { getStoredToken: getStoredToken$2 } = registryAuthController$2;
|
|
56722
57094
|
|
|
56723
57095
|
const REGISTRY_BASE_URL =
|
|
@@ -56992,7 +57364,7 @@ function requireWidgetPublishManifest () {
|
|
|
56992
57364
|
* Mirrors dashboardConfigController patterns for ZIP creation, manifest generation,
|
|
56993
57365
|
* and registry interaction.
|
|
56994
57366
|
*/
|
|
56995
|
-
const path$3 = require$$1$
|
|
57367
|
+
const path$3 = require$$1$1;
|
|
56996
57368
|
const { app: app$3, dialog: dialog$1 } = require$$0$1;
|
|
56997
57369
|
const AdmZip$2 = require$$3$2;
|
|
56998
57370
|
|
|
@@ -57732,7 +58104,7 @@ var themeRegistryController$1 = {
|
|
|
57732
58104
|
*/
|
|
57733
58105
|
|
|
57734
58106
|
const { app: app$2, dialog } = require$$0$1;
|
|
57735
|
-
const path$2 = require$$1$
|
|
58107
|
+
const path$2 = require$$1$1;
|
|
57736
58108
|
const AdmZip$1 = require$$3$2;
|
|
57737
58109
|
const { getFileContents: getFileContents$1 } = file;
|
|
57738
58110
|
const {
|
|
@@ -59701,7 +60073,7 @@ var dashboardConfigController$1 = {
|
|
|
59701
60073
|
*/
|
|
59702
60074
|
|
|
59703
60075
|
const { Notification } = require$$0$1;
|
|
59704
|
-
const Store = require$$
|
|
60076
|
+
const Store = require$$0$6;
|
|
59705
60077
|
|
|
59706
60078
|
const store$1 = new Store({ name: "dash-notifications" });
|
|
59707
60079
|
|
|
@@ -59954,7 +60326,7 @@ var notificationController_1 = notificationController$2;
|
|
|
59954
60326
|
* Multiple widgets referencing the same provider share a single socket.
|
|
59955
60327
|
*/
|
|
59956
60328
|
|
|
59957
|
-
const WebSocket = require$$0$
|
|
60329
|
+
const WebSocket = require$$0$b;
|
|
59958
60330
|
|
|
59959
60331
|
/**
|
|
59960
60332
|
* Active WebSocket connections
|
|
@@ -60944,7 +61316,7 @@ clientCache$1.registerFactory("algolia", (credentials) => {
|
|
|
60944
61316
|
|
|
60945
61317
|
// --- OpenAI ---
|
|
60946
61318
|
clientCache$1.registerFactory("openai", (credentials) => {
|
|
60947
|
-
const OpenAI = require$$0$
|
|
61319
|
+
const OpenAI = require$$0$7;
|
|
60948
61320
|
return new OpenAI({ apiKey: credentials.apiKey });
|
|
60949
61321
|
});
|
|
60950
61322
|
|
|
@@ -60963,7 +61335,7 @@ const MAX_RECENTS = 20;
|
|
|
60963
61335
|
let store = null;
|
|
60964
61336
|
function getStore() {
|
|
60965
61337
|
if (!store) {
|
|
60966
|
-
const Store = require$$
|
|
61338
|
+
const Store = require$$0$6;
|
|
60967
61339
|
store = new Store({ name: "dash-session" });
|
|
60968
61340
|
}
|
|
60969
61341
|
return store;
|
|
@@ -61148,7 +61520,7 @@ var dashboardRatingsUtils = {
|
|
|
61148
61520
|
*/
|
|
61149
61521
|
|
|
61150
61522
|
const { app: app$1 } = require$$0$1;
|
|
61151
|
-
const path$1 = require$$1$
|
|
61523
|
+
const path$1 = require$$1$1;
|
|
61152
61524
|
const { getFileContents, writeToFile } = file;
|
|
61153
61525
|
const {
|
|
61154
61526
|
validateAndBuildRating,
|
|
@@ -61252,7 +61624,7 @@ var dashboardRatingsController = {
|
|
|
61252
61624
|
*/
|
|
61253
61625
|
|
|
61254
61626
|
const fs = require$$0$2;
|
|
61255
|
-
const path = require$$1$
|
|
61627
|
+
const path = require$$1$1;
|
|
61256
61628
|
const AdmZip = require$$3$2;
|
|
61257
61629
|
const { app } = require$$0$1;
|
|
61258
61630
|
|
|
@@ -62057,11 +62429,7 @@ var widgetRegistryController = {
|
|
|
62057
62429
|
*/
|
|
62058
62430
|
|
|
62059
62431
|
const { showDialog, fileChosenError } = dialogController$1;
|
|
62060
|
-
const {
|
|
62061
|
-
isEncryptionAvailable,
|
|
62062
|
-
saveData,
|
|
62063
|
-
getData,
|
|
62064
|
-
} = secureStoreController$1;
|
|
62432
|
+
const { isEncryptionAvailable } = secureStoreController$1;
|
|
62065
62433
|
const {
|
|
62066
62434
|
listWorkspacesForApplication,
|
|
62067
62435
|
saveWorkspaceForApplication,
|
|
@@ -62179,8 +62547,6 @@ var controller = {
|
|
|
62179
62547
|
showDialog,
|
|
62180
62548
|
fileChosenError,
|
|
62181
62549
|
isEncryptionAvailable,
|
|
62182
|
-
saveData,
|
|
62183
|
-
getData,
|
|
62184
62550
|
listWorkspacesForApplication,
|
|
62185
62551
|
saveWorkspaceForApplication,
|
|
62186
62552
|
deleteWorkspaceForApplication,
|
|
@@ -62266,21 +62632,28 @@ var controller = {
|
|
|
62266
62632
|
};
|
|
62267
62633
|
|
|
62268
62634
|
const { ipcRenderer: ipcRenderer$q } = require$$0$1;
|
|
62269
|
-
const {
|
|
62270
|
-
SECURE_STORE_ENCRYPTION_CHECK,
|
|
62271
|
-
SECURE_STORE_SET_DATA,
|
|
62272
|
-
SECURE_STORE_GET_DATA,
|
|
62273
|
-
} = events$8;
|
|
62635
|
+
const { SECURE_STORE_ENCRYPTION_CHECK } = events$8;
|
|
62274
62636
|
/**
|
|
62275
62637
|
* secureStoreApi
|
|
62276
|
-
*
|
|
62638
|
+
*
|
|
62639
|
+
* Renderer-facing wrapper for the secure-store IPC channels. Currently
|
|
62640
|
+
* exposes only `isEncryptionAvailable` because that's the only channel
|
|
62641
|
+
* with a wired handler in dash-electron's main process.
|
|
62642
|
+
*
|
|
62643
|
+
* `saveData` / `getData` were removed in this slice — the IPC handlers
|
|
62644
|
+
* for `SECURE_STORE_SET_DATA` / `SECURE_STORE_GET_DATA` were never
|
|
62645
|
+
* registered, so the methods silently no-op'd. Worse, they appeared
|
|
62646
|
+
* usable on `mainApi.secureStore` but had no widgetId scoping, so
|
|
62647
|
+
* adding handlers later would have given every widget unscoped access
|
|
62648
|
+
* to every other widget's keys. If you need a widget-facing storage
|
|
62649
|
+
* API in the future, add a `widgetId` parameter and plumb it through a
|
|
62650
|
+
* per-widget gate (see `electron/security/fsGate.js` for the pattern).
|
|
62651
|
+
* The pin in `secureStoreApi.test.js` will fail loudly if the
|
|
62652
|
+
* unscoped methods reappear without a gate.
|
|
62277
62653
|
*/
|
|
62278
62654
|
const secureStoreApi$2 = {
|
|
62279
62655
|
isEncryptionAvailable: () =>
|
|
62280
62656
|
ipcRenderer$q.invoke(SECURE_STORE_ENCRYPTION_CHECK, {}),
|
|
62281
|
-
saveData: (key, value) =>
|
|
62282
|
-
ipcRenderer$q.invoke(SECURE_STORE_SET_DATA, { key, value }),
|
|
62283
|
-
getData: (key) => ipcRenderer$q.invoke(SECURE_STORE_GET_DATA, { key }),
|
|
62284
62657
|
};
|
|
62285
62658
|
|
|
62286
62659
|
var secureStoreApi_1 = secureStoreApi$2;
|
|
@@ -62431,20 +62804,30 @@ const dataApi$2 = {
|
|
|
62431
62804
|
* @param {object} options { filename, extension }
|
|
62432
62805
|
* @param {object} returnEmpty the return empty object
|
|
62433
62806
|
*/
|
|
62434
|
-
saveData: (data, filename, append, returnEmpty,
|
|
62807
|
+
saveData: (data, filename, append, returnEmpty, widgetId = null) =>
|
|
62435
62808
|
ipcRenderer$n.invoke(DATA_SAVE_TO_FILE, {
|
|
62436
62809
|
data,
|
|
62437
62810
|
filename,
|
|
62438
62811
|
append,
|
|
62439
62812
|
returnEmpty,
|
|
62813
|
+
widgetId,
|
|
62440
62814
|
}),
|
|
62441
62815
|
|
|
62442
62816
|
/*
|
|
62443
62817
|
* readData
|
|
62444
62818
|
* @param {string} filename the filename to read (not path)
|
|
62819
|
+
* @param {string|null} widgetId Phase 2 JIT consent — the widget's
|
|
62820
|
+
* package name (e.g. "@trops/notes-summarizer"). Used by the fs
|
|
62821
|
+
* gate to scope per-widget read access. Null disables the gate
|
|
62822
|
+
* for legacy callers (`enforceWidgetMcpPermissions` flag still
|
|
62823
|
+
* gates the gate itself).
|
|
62445
62824
|
*/
|
|
62446
|
-
readData: (filename, returnEmpty = []) =>
|
|
62447
|
-
ipcRenderer$n.invoke(DATA_READ_FROM_FILE, {
|
|
62825
|
+
readData: (filename, returnEmpty = [], widgetId = null) =>
|
|
62826
|
+
ipcRenderer$n.invoke(DATA_READ_FROM_FILE, {
|
|
62827
|
+
filename,
|
|
62828
|
+
returnEmpty,
|
|
62829
|
+
widgetId,
|
|
62830
|
+
}),
|
|
62448
62831
|
|
|
62449
62832
|
/**
|
|
62450
62833
|
* transformFile
|