@craft-native/craft 0.0.19 → 0.0.23
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/index.js +88 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1085,6 +1085,14 @@ class Timer {
|
|
|
1085
1085
|
}
|
|
1086
1086
|
// src/bridge/core.ts
|
|
1087
1087
|
var import_events = require("events");
|
|
1088
|
+
var BridgeErrorCodes = {
|
|
1089
|
+
UNKNOWN: -1,
|
|
1090
|
+
TIMEOUT: -2,
|
|
1091
|
+
QUEUE_FULL: -3,
|
|
1092
|
+
BINARY_DISABLED: -4,
|
|
1093
|
+
EXPECTED_BINARY: -5,
|
|
1094
|
+
BRIDGE_DESTROYED: -6
|
|
1095
|
+
};
|
|
1088
1096
|
var messageIdCounter = 0;
|
|
1089
1097
|
function generateMessageId() {
|
|
1090
1098
|
return `msg_${Date.now()}_${++messageIdCounter}`;
|
|
@@ -1123,7 +1131,8 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1123
1131
|
let data;
|
|
1124
1132
|
try {
|
|
1125
1133
|
data = "detail" in event ? event.detail : typeof event.data === "string" ? JSON.parse(event.data) : event.data;
|
|
1126
|
-
} catch {
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
console.warn("[Craft Bridge] Failed to parse message:", err);
|
|
1127
1136
|
return;
|
|
1128
1137
|
}
|
|
1129
1138
|
if (!data || !data.id)
|
|
@@ -1177,27 +1186,28 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1177
1186
|
break;
|
|
1178
1187
|
}
|
|
1179
1188
|
}
|
|
1180
|
-
async request(method, params) {
|
|
1181
|
-
return this.requestWithRetry(method, params, this.config.retries);
|
|
1189
|
+
async request(method, params, options) {
|
|
1190
|
+
return this.requestWithRetry(method, params, this.config.retries, options?.timeout);
|
|
1182
1191
|
}
|
|
1183
|
-
async requestWithRetry(method, params, retriesLeft) {
|
|
1192
|
+
async requestWithRetry(method, params, retriesLeft, timeout) {
|
|
1184
1193
|
try {
|
|
1185
|
-
return await this.sendRequest(method, params);
|
|
1194
|
+
return await this.sendRequest(method, params, timeout);
|
|
1186
1195
|
} catch (error) {
|
|
1187
1196
|
if (retriesLeft > 0 && this.isRetryableError(error)) {
|
|
1188
1197
|
await this.delay(this.config.retryDelay);
|
|
1189
|
-
return this.requestWithRetry(method, params, retriesLeft - 1);
|
|
1198
|
+
return this.requestWithRetry(method, params, retriesLeft - 1, timeout);
|
|
1190
1199
|
}
|
|
1191
1200
|
throw error;
|
|
1192
1201
|
}
|
|
1193
1202
|
}
|
|
1194
1203
|
isRetryableError(error) {
|
|
1195
1204
|
if (error instanceof BridgeError) {
|
|
1196
|
-
return error.code ===
|
|
1205
|
+
return error.code === BridgeErrorCodes.UNKNOWN || error.code === BridgeErrorCodes.TIMEOUT;
|
|
1197
1206
|
}
|
|
1198
1207
|
return false;
|
|
1199
1208
|
}
|
|
1200
|
-
async sendRequest(method, params) {
|
|
1209
|
+
async sendRequest(method, params, timeout) {
|
|
1210
|
+
const effectiveTimeout = timeout ?? this.config.timeout;
|
|
1201
1211
|
const message = {
|
|
1202
1212
|
id: generateMessageId(),
|
|
1203
1213
|
type: "request",
|
|
@@ -1206,7 +1216,7 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1206
1216
|
};
|
|
1207
1217
|
if (!this.connected && this.config.enableOfflineQueue) {
|
|
1208
1218
|
if (this.offlineQueue.length >= this.config.queueSize) {
|
|
1209
|
-
throw new BridgeError("Offline queue full",
|
|
1219
|
+
throw new BridgeError("Offline queue full", BridgeErrorCodes.QUEUE_FULL);
|
|
1210
1220
|
}
|
|
1211
1221
|
this.offlineQueue.push(message);
|
|
1212
1222
|
return new Promise((resolve, reject) => {
|
|
@@ -1215,20 +1225,20 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1215
1225
|
reject,
|
|
1216
1226
|
timeout: setTimeout(() => {
|
|
1217
1227
|
this.pendingRequests.delete(message.id);
|
|
1218
|
-
reject(new BridgeError("Request timeout",
|
|
1219
|
-
},
|
|
1228
|
+
reject(new BridgeError("Request timeout", BridgeErrorCodes.UNKNOWN));
|
|
1229
|
+
}, effectiveTimeout)
|
|
1220
1230
|
});
|
|
1221
1231
|
});
|
|
1222
1232
|
}
|
|
1223
1233
|
return new Promise((resolve, reject) => {
|
|
1224
|
-
const
|
|
1234
|
+
const timer2 = setTimeout(() => {
|
|
1225
1235
|
this.pendingRequests.delete(message.id);
|
|
1226
|
-
reject(new BridgeError("Request timeout",
|
|
1227
|
-
},
|
|
1236
|
+
reject(new BridgeError("Request timeout", BridgeErrorCodes.UNKNOWN));
|
|
1237
|
+
}, effectiveTimeout);
|
|
1228
1238
|
this.pendingRequests.set(message.id, {
|
|
1229
1239
|
resolve,
|
|
1230
1240
|
reject,
|
|
1231
|
-
timeout
|
|
1241
|
+
timeout: timer2
|
|
1232
1242
|
});
|
|
1233
1243
|
this.send(message);
|
|
1234
1244
|
});
|
|
@@ -1284,20 +1294,20 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1284
1294
|
}
|
|
1285
1295
|
async sendBinary(method, data) {
|
|
1286
1296
|
if (!this.config.enableBinaryTransfer) {
|
|
1287
|
-
throw new BridgeError("Binary transfer not enabled",
|
|
1297
|
+
throw new BridgeError("Binary transfer not enabled", BridgeErrorCodes.BINARY_DISABLED);
|
|
1288
1298
|
}
|
|
1289
1299
|
const base64 = this.arrayBufferToBase64(data instanceof ArrayBuffer ? new Uint8Array(data) : data);
|
|
1290
1300
|
await this.request(method, { _binary: true, data: base64 });
|
|
1291
1301
|
}
|
|
1292
1302
|
async receiveBinary(method, params) {
|
|
1293
1303
|
if (!this.config.enableBinaryTransfer) {
|
|
1294
|
-
throw new BridgeError("Binary transfer not enabled",
|
|
1304
|
+
throw new BridgeError("Binary transfer not enabled", BridgeErrorCodes.BINARY_DISABLED);
|
|
1295
1305
|
}
|
|
1296
1306
|
const result = await this.request(method, params);
|
|
1297
1307
|
if (result._binary) {
|
|
1298
1308
|
return this.base64ToArrayBuffer(result.data);
|
|
1299
1309
|
}
|
|
1300
|
-
throw new BridgeError("Expected binary response",
|
|
1310
|
+
throw new BridgeError("Expected binary response", BridgeErrorCodes.EXPECTED_BINARY);
|
|
1301
1311
|
}
|
|
1302
1312
|
async batch(requests) {
|
|
1303
1313
|
return this.request("_batch", { requests });
|
|
@@ -1376,11 +1386,12 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1376
1386
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1377
1387
|
}
|
|
1378
1388
|
arrayBufferToBase64(buffer) {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1389
|
+
const chunks = [];
|
|
1390
|
+
const chunkSize = 8192;
|
|
1391
|
+
for (let i = 0;i < buffer.byteLength; i += chunkSize) {
|
|
1392
|
+
chunks.push(String.fromCharCode(...buffer.subarray(i, i + chunkSize)));
|
|
1382
1393
|
}
|
|
1383
|
-
return btoa(
|
|
1394
|
+
return btoa(chunks.join(""));
|
|
1384
1395
|
}
|
|
1385
1396
|
base64ToArrayBuffer(base64) {
|
|
1386
1397
|
const binary = atob(base64);
|
|
@@ -1394,7 +1405,7 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
1394
1405
|
destroy() {
|
|
1395
1406
|
for (const [id, pending] of this.pendingRequests) {
|
|
1396
1407
|
clearTimeout(pending.timeout);
|
|
1397
|
-
pending.reject(new BridgeError("Bridge destroyed",
|
|
1408
|
+
pending.reject(new BridgeError("Bridge destroyed", BridgeErrorCodes.BRIDGE_DESTROYED));
|
|
1398
1409
|
}
|
|
1399
1410
|
this.pendingRequests.clear();
|
|
1400
1411
|
this.streams.clear();
|
|
@@ -2345,8 +2356,18 @@ var notify = (options) => appManager.notify(options);
|
|
|
2345
2356
|
var registerShortcut = (accelerator, handler) => appManager.registerShortcut(accelerator, handler);
|
|
2346
2357
|
var unregisterShortcut = (accelerator) => appManager.unregisterShortcut(accelerator);
|
|
2347
2358
|
// src/api/fs.ts
|
|
2359
|
+
function validatePath(path) {
|
|
2360
|
+
const normalized = path.replace(/\\/g, "/");
|
|
2361
|
+
if (normalized.includes("/../") || normalized.startsWith("../") || normalized.endsWith("/..") || normalized === "..") {
|
|
2362
|
+
throw new Error(`Path traversal detected: "${path}" contains ".." segments`);
|
|
2363
|
+
}
|
|
2364
|
+
if (path.includes("\x00")) {
|
|
2365
|
+
throw new Error(`Invalid path: contains null byte`);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2348
2368
|
var fs = {
|
|
2349
2369
|
async readFile(path) {
|
|
2370
|
+
validatePath(path);
|
|
2350
2371
|
if (typeof window !== "undefined" && window.craft?.fs) {
|
|
2351
2372
|
return window.craft.fs.readFile(path);
|
|
2352
2373
|
}
|
|
@@ -2354,6 +2375,7 @@ var fs = {
|
|
|
2354
2375
|
return readFile(path, "utf-8");
|
|
2355
2376
|
},
|
|
2356
2377
|
async writeFile(path, content) {
|
|
2378
|
+
validatePath(path);
|
|
2357
2379
|
if (typeof window !== "undefined" && window.craft?.fs) {
|
|
2358
2380
|
return window.craft.fs.writeFile(path, content);
|
|
2359
2381
|
}
|
|
@@ -2395,6 +2417,7 @@ var fs = {
|
|
|
2395
2417
|
}
|
|
2396
2418
|
};
|
|
2397
2419
|
async function readBinaryFile(path) {
|
|
2420
|
+
validatePath(path);
|
|
2398
2421
|
if (typeof window !== "undefined" && window.craft) {
|
|
2399
2422
|
const response = await window.craft.bridge?.call("fs.readBinaryFile", { path });
|
|
2400
2423
|
return new Uint8Array(response.data);
|
|
@@ -2403,6 +2426,7 @@ async function readBinaryFile(path) {
|
|
|
2403
2426
|
return readFile(path);
|
|
2404
2427
|
}
|
|
2405
2428
|
async function writeBinaryFile(path, data) {
|
|
2429
|
+
validatePath(path);
|
|
2406
2430
|
if (typeof window !== "undefined" && window.craft) {
|
|
2407
2431
|
await window.craft.bridge?.call("fs.writeBinaryFile", { path, data: Array.from(data) });
|
|
2408
2432
|
return;
|
|
@@ -2411,6 +2435,7 @@ async function writeBinaryFile(path, data) {
|
|
|
2411
2435
|
return writeFile(path, data);
|
|
2412
2436
|
}
|
|
2413
2437
|
async function stat(path) {
|
|
2438
|
+
validatePath(path);
|
|
2414
2439
|
if (typeof window !== "undefined" && window.craft) {
|
|
2415
2440
|
const response = await window.craft.bridge?.call("fs.stat", { path });
|
|
2416
2441
|
return {
|
|
@@ -2432,6 +2457,8 @@ async function stat(path) {
|
|
|
2432
2457
|
};
|
|
2433
2458
|
}
|
|
2434
2459
|
async function copy(src, dest) {
|
|
2460
|
+
validatePath(src);
|
|
2461
|
+
validatePath(dest);
|
|
2435
2462
|
if (typeof window !== "undefined" && window.craft) {
|
|
2436
2463
|
await window.craft.bridge?.call("fs.copy", { src, dest });
|
|
2437
2464
|
return;
|
|
@@ -2440,6 +2467,8 @@ async function copy(src, dest) {
|
|
|
2440
2467
|
return cp(src, dest, { recursive: true });
|
|
2441
2468
|
}
|
|
2442
2469
|
async function move(src, dest) {
|
|
2470
|
+
validatePath(src);
|
|
2471
|
+
validatePath(dest);
|
|
2443
2472
|
if (typeof window !== "undefined" && window.craft) {
|
|
2444
2473
|
await window.craft.bridge?.call("fs.move", { src, dest });
|
|
2445
2474
|
return;
|
|
@@ -2448,6 +2477,7 @@ async function move(src, dest) {
|
|
|
2448
2477
|
return rename(src, dest);
|
|
2449
2478
|
}
|
|
2450
2479
|
function watch(path, callback) {
|
|
2480
|
+
validatePath(path);
|
|
2451
2481
|
if (typeof window !== "undefined" && window.craft) {
|
|
2452
2482
|
const watchId = Math.random().toString(36).slice(2);
|
|
2453
2483
|
window.craft.bridge?.call("fs.watch", { path, watchId });
|
|
@@ -2502,6 +2532,10 @@ var db = {
|
|
|
2502
2532
|
}
|
|
2503
2533
|
};
|
|
2504
2534
|
async function openDatabase(name) {
|
|
2535
|
+
const normalized = name.replace(/\\/g, "/");
|
|
2536
|
+
if (normalized.includes("/../") || normalized.startsWith("../") || normalized.endsWith("/..") || normalized === ".." || name.includes("\x00")) {
|
|
2537
|
+
throw new Error(`Invalid database name: "${name}" contains path traversal or invalid characters`);
|
|
2538
|
+
}
|
|
2505
2539
|
return new Database(name);
|
|
2506
2540
|
}
|
|
2507
2541
|
|
|
@@ -2620,7 +2654,8 @@ class KeyValueStore {
|
|
|
2620
2654
|
if (row) {
|
|
2621
2655
|
try {
|
|
2622
2656
|
return JSON.parse(row.value);
|
|
2623
|
-
} catch {
|
|
2657
|
+
} catch (err) {
|
|
2658
|
+
console.debug("[Craft DB] Failed to parse stored value for key, returning raw:", err);
|
|
2624
2659
|
return row.value;
|
|
2625
2660
|
}
|
|
2626
2661
|
}
|
|
@@ -2947,11 +2982,12 @@ function bufferToHex(buffer) {
|
|
|
2947
2982
|
}
|
|
2948
2983
|
function bufferToBase64(buffer) {
|
|
2949
2984
|
const bytes = new Uint8Array(buffer);
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2985
|
+
const chunks = [];
|
|
2986
|
+
const chunkSize = 8192;
|
|
2987
|
+
for (let i = 0;i < bytes.byteLength; i += chunkSize) {
|
|
2988
|
+
chunks.push(String.fromCharCode(...bytes.subarray(i, i + chunkSize)));
|
|
2953
2989
|
}
|
|
2954
|
-
return btoa(
|
|
2990
|
+
return btoa(chunks.join(""));
|
|
2955
2991
|
}
|
|
2956
2992
|
function base64ToBuffer(base64) {
|
|
2957
2993
|
const binary = atob(base64);
|
|
@@ -3036,11 +3072,12 @@ async function verifyPassword(password, hash, salt) {
|
|
|
3036
3072
|
// src/api/process.ts
|
|
3037
3073
|
var env = typeof process !== "undefined" ? process.env : {};
|
|
3038
3074
|
function getPlatform() {
|
|
3039
|
-
if (typeof
|
|
3040
|
-
const
|
|
3041
|
-
if (
|
|
3042
|
-
return
|
|
3043
|
-
|
|
3075
|
+
if (typeof globalThis !== "undefined") {
|
|
3076
|
+
const w = globalThis;
|
|
3077
|
+
if (w.craft?._platform)
|
|
3078
|
+
return w.craft._platform;
|
|
3079
|
+
if (w.__CRAFT_PLATFORM__)
|
|
3080
|
+
return w.__CRAFT_PLATFORM__;
|
|
3044
3081
|
}
|
|
3045
3082
|
if (typeof process !== "undefined" && process.platform) {
|
|
3046
3083
|
return process.platform;
|
|
@@ -5365,10 +5402,12 @@ async function write(data) {
|
|
|
5365
5402
|
}
|
|
5366
5403
|
async function read() {
|
|
5367
5404
|
const [text, html] = await Promise.all([
|
|
5368
|
-
readText().catch(() => {
|
|
5405
|
+
readText().catch((err) => {
|
|
5406
|
+
console.debug("[Craft Clipboard] readText failed:", err);
|
|
5369
5407
|
return;
|
|
5370
5408
|
}),
|
|
5371
|
-
readHTML().catch(() => {
|
|
5409
|
+
readHTML().catch((err) => {
|
|
5410
|
+
console.debug("[Craft Clipboard] readHTML failed:", err);
|
|
5372
5411
|
return;
|
|
5373
5412
|
})
|
|
5374
5413
|
]);
|
|
@@ -5822,23 +5861,26 @@ function generateCSS(config, platform) {
|
|
|
5822
5861
|
}
|
|
5823
5862
|
return baseStyles + platformStyles;
|
|
5824
5863
|
}
|
|
5864
|
+
function escapeHtml(unsafe) {
|
|
5865
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5866
|
+
}
|
|
5825
5867
|
function generateHTML(config) {
|
|
5826
5868
|
const renderItem = (item) => `
|
|
5827
|
-
<div class='craft-sidebar-item${item.selected ? " selected" : ""}' data-id='${item.id}'>
|
|
5869
|
+
<div class='craft-sidebar-item${item.selected ? " selected" : ""}' data-id='${escapeHtml(item.id)}'>
|
|
5828
5870
|
${item.icon ? `<span class='craft-sidebar-item-icon'>${getIcon(item.icon, item.tintColor)}</span>` : ""}
|
|
5829
|
-
<span class='craft-sidebar-item-label'>${item.label}</span>
|
|
5830
|
-
${item.badge !== undefined ? `<span class='craft-sidebar-item-badge'>${item.badge}</span>` : ""}
|
|
5871
|
+
<span class='craft-sidebar-item-label'>${escapeHtml(item.label)}</span>
|
|
5872
|
+
${item.badge !== undefined ? `<span class='craft-sidebar-item-badge'>${escapeHtml(String(item.badge))}</span>` : ""}
|
|
5831
5873
|
</div>
|
|
5832
5874
|
`;
|
|
5833
5875
|
const renderSection = (section) => `
|
|
5834
|
-
<div class='craft-sidebar-section' data-section='${section.id}'>
|
|
5876
|
+
<div class='craft-sidebar-section' data-section='${escapeHtml(section.id)}'>
|
|
5835
5877
|
${section.title ? `
|
|
5836
|
-
<div class='craft-sidebar-section-header${section.collapsed ? " collapsed" : ""}' data-section-toggle='${section.id}'>
|
|
5878
|
+
<div class='craft-sidebar-section-header${section.collapsed ? " collapsed" : ""}' data-section-toggle='${escapeHtml(section.id)}'>
|
|
5837
5879
|
${section.collapsible !== false ? `<svg class="craft-sidebar-section-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>` : ""}
|
|
5838
|
-
${section.title}
|
|
5880
|
+
${escapeHtml(section.title)}
|
|
5839
5881
|
</div>
|
|
5840
5882
|
` : ""}
|
|
5841
|
-
<div class='craft-sidebar-section-items${section.collapsed ? " collapsed" : ""}' data-section-items='${section.id}'>
|
|
5883
|
+
<div class='craft-sidebar-section-items${section.collapsed ? " collapsed" : ""}' data-section-items='${escapeHtml(section.id)}'>
|
|
5842
5884
|
${section.items.map(renderItem).join("")}
|
|
5843
5885
|
</div>
|
|
5844
5886
|
</div>
|
|
@@ -5847,13 +5889,13 @@ function generateHTML(config) {
|
|
|
5847
5889
|
<aside class='craft-sidebar'>
|
|
5848
5890
|
${config.headerTitle ? `
|
|
5849
5891
|
<div class='craft-sidebar-header'>
|
|
5850
|
-
<div class='craft-sidebar-header-title'>${config.headerTitle}</div>
|
|
5851
|
-
${config.headerSubtitle ? `<div class="craft-sidebar-header-subtitle">${config.headerSubtitle}</div>` : ""}
|
|
5892
|
+
<div class='craft-sidebar-header-title'>${escapeHtml(config.headerTitle)}</div>
|
|
5893
|
+
${config.headerSubtitle ? `<div class="craft-sidebar-header-subtitle">${escapeHtml(config.headerSubtitle)}</div>` : ""}
|
|
5852
5894
|
</div>
|
|
5853
5895
|
` : ""}
|
|
5854
5896
|
${config.showSearch ? `
|
|
5855
5897
|
<div class='craft-sidebar-search'>
|
|
5856
|
-
<input type='text' placeholder='${config.searchPlaceholder || "Search"}' data-sidebar-search>
|
|
5898
|
+
<input type='text' placeholder='${escapeHtml(config.searchPlaceholder || "Search")}' data-sidebar-search>
|
|
5857
5899
|
</div>
|
|
5858
5900
|
` : ""}
|
|
5859
5901
|
<div class='craft-sidebar-content'>
|
|
@@ -5864,7 +5906,7 @@ function generateHTML(config) {
|
|
|
5864
5906
|
`;
|
|
5865
5907
|
}
|
|
5866
5908
|
function getIcon(name, tintColor) {
|
|
5867
|
-
const color = tintColor || "currentColor";
|
|
5909
|
+
const color = escapeHtml(tintColor || "currentColor");
|
|
5868
5910
|
const icons = {
|
|
5869
5911
|
folder: `<svg viewBox='0 0 24 24' fill='${color}'><path d='M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z'/></svg>`,
|
|
5870
5912
|
file: `<svg viewBox='0 0 24 24' fill='none' stroke='${color}' stroke-width='2'><path d='M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z'/><polyline points='13 2 13 9 20 9'/></svg>`,
|