craft-native 0.0.499 → 0.0.500
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/android/templates/CraftNativeFlatList.kt.template +51 -3
- package/dist/android/templates/CraftNativeShell.kt.template +54 -0
- package/dist/android/templates/MainActivityNative.kt.template +25 -1
- package/dist/api/process.d.ts +23 -28
- package/dist/bridge/core.d.ts +30 -6
- package/dist/cli.js +39 -19
- package/dist/components/index.d.ts +1 -1
- package/dist/index.cjs +90 -99
- package/dist/index.js +90 -99
- package/dist/ios/templates/CraftApp.swift +17 -0
- package/dist/ios/templates/CraftHybrid.swift +16 -0
- package/dist/ios/templates/CraftNativeFiles.swift +152 -7
- package/dist/ios/templates/CraftNativeFlatList.swift +43 -6
- package/dist/ios/templates/CraftNativeScreen.swift +184 -66
- package/dist/ios/templates/CraftScheduledWorkouts.swift +11 -4
- package/dist/ios/templates/CraftWebAudio.swift +20 -1
- package/dist/package.d.ts +22 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -376,7 +376,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
376
376
|
`);
|
|
377
377
|
}
|
|
378
378
|
// package.json
|
|
379
|
-
var version = "0.0.
|
|
379
|
+
var version = "0.0.500";
|
|
380
380
|
|
|
381
381
|
// src/sdk-version.ts
|
|
382
382
|
var SDK_VERSION = version;
|
|
@@ -1764,6 +1764,33 @@ Description: ${description || opts.name}
|
|
|
1764
1764
|
}
|
|
1765
1765
|
});
|
|
1766
1766
|
}
|
|
1767
|
+
function rpmSpec(options) {
|
|
1768
|
+
return [
|
|
1769
|
+
"%global debug_package %{nil}",
|
|
1770
|
+
"%global _build_id_links none",
|
|
1771
|
+
"%global __strip /bin/true",
|
|
1772
|
+
"%global __os_install_post %{nil}",
|
|
1773
|
+
`Name: ${options.name}`,
|
|
1774
|
+
`Version: ${options.version}`,
|
|
1775
|
+
"Release: 1",
|
|
1776
|
+
`Summary: ${options.summary}`,
|
|
1777
|
+
"License: MIT",
|
|
1778
|
+
`Source0: ${options.name}`,
|
|
1779
|
+
...options.requires ? [`Requires: ${options.requires}`] : [],
|
|
1780
|
+
"",
|
|
1781
|
+
"%description",
|
|
1782
|
+
options.description,
|
|
1783
|
+
"",
|
|
1784
|
+
"%install",
|
|
1785
|
+
"mkdir -p %{buildroot}/usr/bin",
|
|
1786
|
+
`install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${options.name}`,
|
|
1787
|
+
"",
|
|
1788
|
+
"%files",
|
|
1789
|
+
`/usr/bin/${options.name}`,
|
|
1790
|
+
""
|
|
1791
|
+
].join(`
|
|
1792
|
+
`);
|
|
1793
|
+
}
|
|
1767
1794
|
async function createRPM(opts) {
|
|
1768
1795
|
return new Promise((resolve) => {
|
|
1769
1796
|
try {
|
|
@@ -1796,26 +1823,17 @@ async function createRPM(opts) {
|
|
|
1796
1823
|
return r;
|
|
1797
1824
|
}).join(", ");
|
|
1798
1825
|
import_fs.copyFileSync(opts.binaryPath, import_path.join(sourcesDir, sanitizedName));
|
|
1799
|
-
const specContent =
|
|
1800
|
-
Version: ${opts.version}
|
|
1801
|
-
Release: 1%{?dist}
|
|
1802
|
-
Summary: ${summary}
|
|
1803
|
-
License: MIT
|
|
1804
|
-
Requires: ${requires}
|
|
1805
|
-
|
|
1806
|
-
%description
|
|
1807
|
-
${description}
|
|
1808
|
-
|
|
1809
|
-
%install
|
|
1810
|
-
mkdir -p %{buildroot}/usr/bin
|
|
1811
|
-
install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
|
|
1812
|
-
|
|
1813
|
-
%files
|
|
1814
|
-
/usr/bin/${sanitizedName}
|
|
1815
|
-
`;
|
|
1826
|
+
const specContent = rpmSpec({ name: sanitizedName, version: opts.version, summary, description, requires });
|
|
1816
1827
|
const specPath = import_path.join(specDir, `${opts.name.toLowerCase()}.spec`);
|
|
1817
1828
|
import_fs.writeFileSync(specPath, specContent);
|
|
1818
1829
|
const proc = import_child_process.spawn("rpmbuild", ["-bb", specPath]);
|
|
1830
|
+
let said = "";
|
|
1831
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1832
|
+
said += chunk.toString();
|
|
1833
|
+
});
|
|
1834
|
+
proc.stderr?.on("data", (chunk) => {
|
|
1835
|
+
said += chunk.toString();
|
|
1836
|
+
});
|
|
1819
1837
|
proc.on("close", (code) => {
|
|
1820
1838
|
if (code === 0) {
|
|
1821
1839
|
const rpmName = `${opts.name.toLowerCase()}-${opts.version}-1.x86_64.rpm`;
|
|
@@ -1827,7 +1845,9 @@ install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
|
|
|
1827
1845
|
resolve({ success: false, error: "Failed to copy built RPM" });
|
|
1828
1846
|
}
|
|
1829
1847
|
} else {
|
|
1830
|
-
resolve({ success: false, error: `rpmbuild exited with code ${code}
|
|
1848
|
+
resolve({ success: false, error: `rpmbuild exited with code ${code}: ${said.trim().split(`
|
|
1849
|
+
`).slice(-12).join(`
|
|
1850
|
+
`)}` });
|
|
1831
1851
|
}
|
|
1832
1852
|
});
|
|
1833
1853
|
proc.on("error", (err) => {
|
|
@@ -2364,7 +2384,8 @@ var BridgeErrorCodes = {
|
|
|
2364
2384
|
BRIDGE_DESTROYED: -6,
|
|
2365
2385
|
BUSY: -7,
|
|
2366
2386
|
PROTOCOL_MISMATCH: -8,
|
|
2367
|
-
DISCONNECTED: -9
|
|
2387
|
+
DISCONNECTED: -9,
|
|
2388
|
+
NATIVE_ERROR: -10
|
|
2368
2389
|
};
|
|
2369
2390
|
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
2370
2391
|
function buildMessageId(prefix, counter) {
|
|
@@ -2772,6 +2793,9 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
2772
2793
|
}
|
|
2773
2794
|
try {
|
|
2774
2795
|
if (typeof window !== "undefined") {
|
|
2796
|
+
if (message.type === "request" && this.sendThroughInjectedBridge(message)) {
|
|
2797
|
+
return true;
|
|
2798
|
+
}
|
|
2775
2799
|
if (window.webkit?.messageHandlers?.craft) {
|
|
2776
2800
|
window.webkit.messageHandlers.craft.postMessage(message);
|
|
2777
2801
|
return true;
|
|
@@ -2799,6 +2823,31 @@ class NativeBridge extends import_events.EventEmitter {
|
|
|
2799
2823
|
return false;
|
|
2800
2824
|
}
|
|
2801
2825
|
}
|
|
2826
|
+
sendThroughInjectedBridge(message) {
|
|
2827
|
+
const injected = window.craft;
|
|
2828
|
+
if (!injected || typeof injected.invoke !== "function" || typeof message.method !== "string")
|
|
2829
|
+
return false;
|
|
2830
|
+
let call;
|
|
2831
|
+
try {
|
|
2832
|
+
call = Promise.resolve(injected.invoke(message.method, message.params));
|
|
2833
|
+
} catch (e) {
|
|
2834
|
+
call = Promise.reject(e);
|
|
2835
|
+
}
|
|
2836
|
+
call.then((result) => this.handleResponse({ id: message.id, type: "response", result }), (error) => {
|
|
2837
|
+
const detail = error ?? {};
|
|
2838
|
+
const text = typeof detail.message === "string" && detail.message.length > 0 ? detail.message : `Native bridge refused ${message.method}`;
|
|
2839
|
+
this.handleResponse({
|
|
2840
|
+
id: message.id,
|
|
2841
|
+
type: "response",
|
|
2842
|
+
error: {
|
|
2843
|
+
code: BridgeErrorCodes.NATIVE_ERROR,
|
|
2844
|
+
message: text,
|
|
2845
|
+
data: { nativeCode: typeof detail.code === "string" ? detail.code : undefined, method: message.method }
|
|
2846
|
+
}
|
|
2847
|
+
});
|
|
2848
|
+
});
|
|
2849
|
+
return true;
|
|
2850
|
+
}
|
|
2802
2851
|
failPendingForMessage(message, error) {
|
|
2803
2852
|
const pending = this.pendingRequests.get(message.id);
|
|
2804
2853
|
if (!pending)
|
|
@@ -5109,7 +5158,7 @@ async function getSystemInfo() {
|
|
|
5109
5158
|
}
|
|
5110
5159
|
async function exec(command, options) {
|
|
5111
5160
|
if (typeof window !== "undefined" && window.craft) {
|
|
5112
|
-
return callBridge3("
|
|
5161
|
+
return callBridge3("shell.exec", { command, cwd: options?.cwd });
|
|
5113
5162
|
}
|
|
5114
5163
|
const { exec: nodeExec } = await import("node:child_process");
|
|
5115
5164
|
const { promisify } = await import("node:util");
|
|
@@ -5130,107 +5179,46 @@ async function exec(command, options) {
|
|
|
5130
5179
|
};
|
|
5131
5180
|
}
|
|
5132
5181
|
}
|
|
5182
|
+
var nextProcessId = 0;
|
|
5183
|
+
function shellFacade() {
|
|
5184
|
+
return window.craft?.shell;
|
|
5185
|
+
}
|
|
5133
5186
|
|
|
5134
5187
|
class ChildProcess {
|
|
5135
5188
|
command;
|
|
5136
5189
|
args;
|
|
5137
5190
|
options;
|
|
5138
5191
|
processId = null;
|
|
5139
|
-
onStdoutHandlers = [];
|
|
5140
|
-
onStderrHandlers = [];
|
|
5141
|
-
onExitHandlers = [];
|
|
5142
|
-
domListeners = [];
|
|
5143
|
-
exited = false;
|
|
5144
5192
|
constructor(command, args = [], options = {}) {
|
|
5145
5193
|
this.command = command;
|
|
5146
5194
|
this.args = args;
|
|
5147
5195
|
this.options = options;
|
|
5148
5196
|
}
|
|
5149
|
-
addDomListener(type, handler) {
|
|
5150
|
-
if (typeof window === "undefined")
|
|
5151
|
-
return;
|
|
5152
|
-
window.addEventListener(type, handler);
|
|
5153
|
-
this.domListeners.push({ type, handler });
|
|
5154
|
-
}
|
|
5155
|
-
removeDomListeners() {
|
|
5156
|
-
if (typeof window === "undefined")
|
|
5157
|
-
return;
|
|
5158
|
-
for (const { type, handler } of this.domListeners) {
|
|
5159
|
-
window.removeEventListener(type, handler);
|
|
5160
|
-
}
|
|
5161
|
-
this.domListeners = [];
|
|
5162
|
-
}
|
|
5163
5197
|
get pid() {
|
|
5164
5198
|
return this.processId;
|
|
5165
5199
|
}
|
|
5166
5200
|
async start() {
|
|
5167
5201
|
if (typeof window !== "undefined" && window.craft) {
|
|
5168
|
-
|
|
5202
|
+
nextProcessId += 1;
|
|
5203
|
+
const id = `sdk-${Date.now().toString(36)}-${nextProcessId}`;
|
|
5204
|
+
await callBridge3("shell.spawn", {
|
|
5205
|
+
id,
|
|
5169
5206
|
command: this.command,
|
|
5170
5207
|
args: this.args,
|
|
5171
|
-
|
|
5208
|
+
cwd: this.options.cwd,
|
|
5209
|
+
shell: this.options.shell
|
|
5172
5210
|
});
|
|
5173
|
-
this.processId =
|
|
5174
|
-
const stdoutHandler = (event) => {
|
|
5175
|
-
if (event.detail.pid === this.processId) {
|
|
5176
|
-
this.onStdoutHandlers.forEach((h) => h(event.detail.data));
|
|
5177
|
-
}
|
|
5178
|
-
};
|
|
5179
|
-
const stderrHandler = (event) => {
|
|
5180
|
-
if (event.detail.pid === this.processId) {
|
|
5181
|
-
this.onStderrHandlers.forEach((h) => h(event.detail.data));
|
|
5182
|
-
}
|
|
5183
|
-
};
|
|
5184
|
-
const exitHandler = (event) => {
|
|
5185
|
-
if (event.detail.pid === this.processId) {
|
|
5186
|
-
this.exited = true;
|
|
5187
|
-
this.onExitHandlers.forEach((h) => h(event.detail.code));
|
|
5188
|
-
this.removeDomListeners();
|
|
5189
|
-
}
|
|
5190
|
-
};
|
|
5191
|
-
this.addDomListener("craft:process:stdout", stdoutHandler);
|
|
5192
|
-
this.addDomListener("craft:process:stderr", stderrHandler);
|
|
5193
|
-
this.addDomListener("craft:process:exit", exitHandler);
|
|
5211
|
+
this.processId = id;
|
|
5194
5212
|
return;
|
|
5195
5213
|
}
|
|
5196
5214
|
throw new Error("spawn() requires Craft environment");
|
|
5197
5215
|
}
|
|
5198
|
-
async
|
|
5216
|
+
async kill() {
|
|
5199
5217
|
if (typeof window !== "undefined" && window.craft && this.processId) {
|
|
5200
|
-
await
|
|
5218
|
+
await shellFacade()?.kill?.(this.processId);
|
|
5219
|
+
this.processId = null;
|
|
5201
5220
|
}
|
|
5202
5221
|
}
|
|
5203
|
-
async kill(signal = "SIGTERM") {
|
|
5204
|
-
if (typeof window !== "undefined" && window.craft && this.processId) {
|
|
5205
|
-
await callBridge3("process.kill", { pid: this.processId, signal });
|
|
5206
|
-
if (this.exited)
|
|
5207
|
-
this.removeDomListeners();
|
|
5208
|
-
}
|
|
5209
|
-
}
|
|
5210
|
-
onStdout(handler) {
|
|
5211
|
-
this.onStdoutHandlers.push(handler);
|
|
5212
|
-
return () => {
|
|
5213
|
-
const idx = this.onStdoutHandlers.indexOf(handler);
|
|
5214
|
-
if (idx >= 0)
|
|
5215
|
-
this.onStdoutHandlers.splice(idx, 1);
|
|
5216
|
-
};
|
|
5217
|
-
}
|
|
5218
|
-
onStderr(handler) {
|
|
5219
|
-
this.onStderrHandlers.push(handler);
|
|
5220
|
-
return () => {
|
|
5221
|
-
const idx = this.onStderrHandlers.indexOf(handler);
|
|
5222
|
-
if (idx >= 0)
|
|
5223
|
-
this.onStderrHandlers.splice(idx, 1);
|
|
5224
|
-
};
|
|
5225
|
-
}
|
|
5226
|
-
onExit(handler) {
|
|
5227
|
-
this.onExitHandlers.push(handler);
|
|
5228
|
-
return () => {
|
|
5229
|
-
const idx = this.onExitHandlers.indexOf(handler);
|
|
5230
|
-
if (idx >= 0)
|
|
5231
|
-
this.onExitHandlers.splice(idx, 1);
|
|
5232
|
-
};
|
|
5233
|
-
}
|
|
5234
5222
|
}
|
|
5235
5223
|
function spawn2(command, args, options) {
|
|
5236
5224
|
return new ChildProcess(command, args, options);
|
|
@@ -5243,7 +5231,7 @@ function cwd() {
|
|
|
5243
5231
|
}
|
|
5244
5232
|
async function homeDir() {
|
|
5245
5233
|
if (typeof window !== "undefined" && window.craft) {
|
|
5246
|
-
return callBridge3("
|
|
5234
|
+
return (await callBridge3("fs.getHomeDir"))?.path || "/";
|
|
5247
5235
|
}
|
|
5248
5236
|
if (typeof process !== "undefined") {
|
|
5249
5237
|
const os = await import("node:os");
|
|
@@ -5253,7 +5241,7 @@ async function homeDir() {
|
|
|
5253
5241
|
}
|
|
5254
5242
|
async function tempDir() {
|
|
5255
5243
|
if (typeof window !== "undefined" && window.craft) {
|
|
5256
|
-
return callBridge3("
|
|
5244
|
+
return (await callBridge3("fs.getTempDir"))?.path || "/tmp";
|
|
5257
5245
|
}
|
|
5258
5246
|
if (typeof process !== "undefined") {
|
|
5259
5247
|
const os = await import("node:os");
|
|
@@ -5281,7 +5269,10 @@ function argv() {
|
|
|
5281
5269
|
}
|
|
5282
5270
|
async function open(target) {
|
|
5283
5271
|
if (typeof window !== "undefined" && window.craft) {
|
|
5284
|
-
|
|
5272
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\/]/i.test(target))
|
|
5273
|
+
await callBridge3("shell.openUrl", { url: target });
|
|
5274
|
+
else
|
|
5275
|
+
await shellFacade()?.openPath?.(target);
|
|
5285
5276
|
return;
|
|
5286
5277
|
}
|
|
5287
5278
|
const platform = getPlatform();
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
117
117
|
`);
|
|
118
118
|
}
|
|
119
119
|
// package.json
|
|
120
|
-
var version = "0.0.
|
|
120
|
+
var version = "0.0.500";
|
|
121
121
|
|
|
122
122
|
// src/sdk-version.ts
|
|
123
123
|
var SDK_VERSION = version;
|
|
@@ -1520,6 +1520,33 @@ Description: ${description || opts.name}
|
|
|
1520
1520
|
}
|
|
1521
1521
|
});
|
|
1522
1522
|
}
|
|
1523
|
+
function rpmSpec(options) {
|
|
1524
|
+
return [
|
|
1525
|
+
"%global debug_package %{nil}",
|
|
1526
|
+
"%global _build_id_links none",
|
|
1527
|
+
"%global __strip /bin/true",
|
|
1528
|
+
"%global __os_install_post %{nil}",
|
|
1529
|
+
`Name: ${options.name}`,
|
|
1530
|
+
`Version: ${options.version}`,
|
|
1531
|
+
"Release: 1",
|
|
1532
|
+
`Summary: ${options.summary}`,
|
|
1533
|
+
"License: MIT",
|
|
1534
|
+
`Source0: ${options.name}`,
|
|
1535
|
+
...options.requires ? [`Requires: ${options.requires}`] : [],
|
|
1536
|
+
"",
|
|
1537
|
+
"%description",
|
|
1538
|
+
options.description,
|
|
1539
|
+
"",
|
|
1540
|
+
"%install",
|
|
1541
|
+
"mkdir -p %{buildroot}/usr/bin",
|
|
1542
|
+
`install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${options.name}`,
|
|
1543
|
+
"",
|
|
1544
|
+
"%files",
|
|
1545
|
+
`/usr/bin/${options.name}`,
|
|
1546
|
+
""
|
|
1547
|
+
].join(`
|
|
1548
|
+
`);
|
|
1549
|
+
}
|
|
1523
1550
|
async function createRPM(opts) {
|
|
1524
1551
|
return new Promise((resolve) => {
|
|
1525
1552
|
try {
|
|
@@ -1552,26 +1579,17 @@ async function createRPM(opts) {
|
|
|
1552
1579
|
return r;
|
|
1553
1580
|
}).join(", ");
|
|
1554
1581
|
copyFileSync(opts.binaryPath, join3(sourcesDir, sanitizedName));
|
|
1555
|
-
const specContent =
|
|
1556
|
-
Version: ${opts.version}
|
|
1557
|
-
Release: 1%{?dist}
|
|
1558
|
-
Summary: ${summary}
|
|
1559
|
-
License: MIT
|
|
1560
|
-
Requires: ${requires}
|
|
1561
|
-
|
|
1562
|
-
%description
|
|
1563
|
-
${description}
|
|
1564
|
-
|
|
1565
|
-
%install
|
|
1566
|
-
mkdir -p %{buildroot}/usr/bin
|
|
1567
|
-
install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
|
|
1568
|
-
|
|
1569
|
-
%files
|
|
1570
|
-
/usr/bin/${sanitizedName}
|
|
1571
|
-
`;
|
|
1582
|
+
const specContent = rpmSpec({ name: sanitizedName, version: opts.version, summary, description, requires });
|
|
1572
1583
|
const specPath = join3(specDir, `${opts.name.toLowerCase()}.spec`);
|
|
1573
1584
|
writeFileSync3(specPath, specContent);
|
|
1574
1585
|
const proc = spawn("rpmbuild", ["-bb", specPath]);
|
|
1586
|
+
let said = "";
|
|
1587
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1588
|
+
said += chunk.toString();
|
|
1589
|
+
});
|
|
1590
|
+
proc.stderr?.on("data", (chunk) => {
|
|
1591
|
+
said += chunk.toString();
|
|
1592
|
+
});
|
|
1575
1593
|
proc.on("close", (code) => {
|
|
1576
1594
|
if (code === 0) {
|
|
1577
1595
|
const rpmName = `${opts.name.toLowerCase()}-${opts.version}-1.x86_64.rpm`;
|
|
@@ -1583,7 +1601,9 @@ install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
|
|
|
1583
1601
|
resolve({ success: false, error: "Failed to copy built RPM" });
|
|
1584
1602
|
}
|
|
1585
1603
|
} else {
|
|
1586
|
-
resolve({ success: false, error: `rpmbuild exited with code ${code}
|
|
1604
|
+
resolve({ success: false, error: `rpmbuild exited with code ${code}: ${said.trim().split(`
|
|
1605
|
+
`).slice(-12).join(`
|
|
1606
|
+
`)}` });
|
|
1587
1607
|
}
|
|
1588
1608
|
});
|
|
1589
1609
|
proc.on("error", (err) => {
|
|
@@ -2120,7 +2140,8 @@ var BridgeErrorCodes = {
|
|
|
2120
2140
|
BRIDGE_DESTROYED: -6,
|
|
2121
2141
|
BUSY: -7,
|
|
2122
2142
|
PROTOCOL_MISMATCH: -8,
|
|
2123
|
-
DISCONNECTED: -9
|
|
2143
|
+
DISCONNECTED: -9,
|
|
2144
|
+
NATIVE_ERROR: -10
|
|
2124
2145
|
};
|
|
2125
2146
|
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
2126
2147
|
function buildMessageId(prefix, counter) {
|
|
@@ -2528,6 +2549,9 @@ class NativeBridge extends EventEmitter {
|
|
|
2528
2549
|
}
|
|
2529
2550
|
try {
|
|
2530
2551
|
if (typeof window !== "undefined") {
|
|
2552
|
+
if (message.type === "request" && this.sendThroughInjectedBridge(message)) {
|
|
2553
|
+
return true;
|
|
2554
|
+
}
|
|
2531
2555
|
if (window.webkit?.messageHandlers?.craft) {
|
|
2532
2556
|
window.webkit.messageHandlers.craft.postMessage(message);
|
|
2533
2557
|
return true;
|
|
@@ -2555,6 +2579,31 @@ class NativeBridge extends EventEmitter {
|
|
|
2555
2579
|
return false;
|
|
2556
2580
|
}
|
|
2557
2581
|
}
|
|
2582
|
+
sendThroughInjectedBridge(message) {
|
|
2583
|
+
const injected = window.craft;
|
|
2584
|
+
if (!injected || typeof injected.invoke !== "function" || typeof message.method !== "string")
|
|
2585
|
+
return false;
|
|
2586
|
+
let call;
|
|
2587
|
+
try {
|
|
2588
|
+
call = Promise.resolve(injected.invoke(message.method, message.params));
|
|
2589
|
+
} catch (e) {
|
|
2590
|
+
call = Promise.reject(e);
|
|
2591
|
+
}
|
|
2592
|
+
call.then((result) => this.handleResponse({ id: message.id, type: "response", result }), (error) => {
|
|
2593
|
+
const detail = error ?? {};
|
|
2594
|
+
const text = typeof detail.message === "string" && detail.message.length > 0 ? detail.message : `Native bridge refused ${message.method}`;
|
|
2595
|
+
this.handleResponse({
|
|
2596
|
+
id: message.id,
|
|
2597
|
+
type: "response",
|
|
2598
|
+
error: {
|
|
2599
|
+
code: BridgeErrorCodes.NATIVE_ERROR,
|
|
2600
|
+
message: text,
|
|
2601
|
+
data: { nativeCode: typeof detail.code === "string" ? detail.code : undefined, method: message.method }
|
|
2602
|
+
}
|
|
2603
|
+
});
|
|
2604
|
+
});
|
|
2605
|
+
return true;
|
|
2606
|
+
}
|
|
2558
2607
|
failPendingForMessage(message, error) {
|
|
2559
2608
|
const pending = this.pendingRequests.get(message.id);
|
|
2560
2609
|
if (!pending)
|
|
@@ -4865,7 +4914,7 @@ async function getSystemInfo() {
|
|
|
4865
4914
|
}
|
|
4866
4915
|
async function exec(command, options) {
|
|
4867
4916
|
if (typeof window !== "undefined" && window.craft) {
|
|
4868
|
-
return callBridge3("
|
|
4917
|
+
return callBridge3("shell.exec", { command, cwd: options?.cwd });
|
|
4869
4918
|
}
|
|
4870
4919
|
const { exec: nodeExec } = await import("child_process");
|
|
4871
4920
|
const { promisify } = await import("util");
|
|
@@ -4886,107 +4935,46 @@ async function exec(command, options) {
|
|
|
4886
4935
|
};
|
|
4887
4936
|
}
|
|
4888
4937
|
}
|
|
4938
|
+
var nextProcessId = 0;
|
|
4939
|
+
function shellFacade() {
|
|
4940
|
+
return window.craft?.shell;
|
|
4941
|
+
}
|
|
4889
4942
|
|
|
4890
4943
|
class ChildProcess {
|
|
4891
4944
|
command;
|
|
4892
4945
|
args;
|
|
4893
4946
|
options;
|
|
4894
4947
|
processId = null;
|
|
4895
|
-
onStdoutHandlers = [];
|
|
4896
|
-
onStderrHandlers = [];
|
|
4897
|
-
onExitHandlers = [];
|
|
4898
|
-
domListeners = [];
|
|
4899
|
-
exited = false;
|
|
4900
4948
|
constructor(command, args = [], options = {}) {
|
|
4901
4949
|
this.command = command;
|
|
4902
4950
|
this.args = args;
|
|
4903
4951
|
this.options = options;
|
|
4904
4952
|
}
|
|
4905
|
-
addDomListener(type, handler) {
|
|
4906
|
-
if (typeof window === "undefined")
|
|
4907
|
-
return;
|
|
4908
|
-
window.addEventListener(type, handler);
|
|
4909
|
-
this.domListeners.push({ type, handler });
|
|
4910
|
-
}
|
|
4911
|
-
removeDomListeners() {
|
|
4912
|
-
if (typeof window === "undefined")
|
|
4913
|
-
return;
|
|
4914
|
-
for (const { type, handler } of this.domListeners) {
|
|
4915
|
-
window.removeEventListener(type, handler);
|
|
4916
|
-
}
|
|
4917
|
-
this.domListeners = [];
|
|
4918
|
-
}
|
|
4919
4953
|
get pid() {
|
|
4920
4954
|
return this.processId;
|
|
4921
4955
|
}
|
|
4922
4956
|
async start() {
|
|
4923
4957
|
if (typeof window !== "undefined" && window.craft) {
|
|
4924
|
-
|
|
4958
|
+
nextProcessId += 1;
|
|
4959
|
+
const id = `sdk-${Date.now().toString(36)}-${nextProcessId}`;
|
|
4960
|
+
await callBridge3("shell.spawn", {
|
|
4961
|
+
id,
|
|
4925
4962
|
command: this.command,
|
|
4926
4963
|
args: this.args,
|
|
4927
|
-
|
|
4964
|
+
cwd: this.options.cwd,
|
|
4965
|
+
shell: this.options.shell
|
|
4928
4966
|
});
|
|
4929
|
-
this.processId =
|
|
4930
|
-
const stdoutHandler = (event) => {
|
|
4931
|
-
if (event.detail.pid === this.processId) {
|
|
4932
|
-
this.onStdoutHandlers.forEach((h) => h(event.detail.data));
|
|
4933
|
-
}
|
|
4934
|
-
};
|
|
4935
|
-
const stderrHandler = (event) => {
|
|
4936
|
-
if (event.detail.pid === this.processId) {
|
|
4937
|
-
this.onStderrHandlers.forEach((h) => h(event.detail.data));
|
|
4938
|
-
}
|
|
4939
|
-
};
|
|
4940
|
-
const exitHandler = (event) => {
|
|
4941
|
-
if (event.detail.pid === this.processId) {
|
|
4942
|
-
this.exited = true;
|
|
4943
|
-
this.onExitHandlers.forEach((h) => h(event.detail.code));
|
|
4944
|
-
this.removeDomListeners();
|
|
4945
|
-
}
|
|
4946
|
-
};
|
|
4947
|
-
this.addDomListener("craft:process:stdout", stdoutHandler);
|
|
4948
|
-
this.addDomListener("craft:process:stderr", stderrHandler);
|
|
4949
|
-
this.addDomListener("craft:process:exit", exitHandler);
|
|
4967
|
+
this.processId = id;
|
|
4950
4968
|
return;
|
|
4951
4969
|
}
|
|
4952
4970
|
throw new Error("spawn() requires Craft environment");
|
|
4953
4971
|
}
|
|
4954
|
-
async
|
|
4972
|
+
async kill() {
|
|
4955
4973
|
if (typeof window !== "undefined" && window.craft && this.processId) {
|
|
4956
|
-
await
|
|
4974
|
+
await shellFacade()?.kill?.(this.processId);
|
|
4975
|
+
this.processId = null;
|
|
4957
4976
|
}
|
|
4958
4977
|
}
|
|
4959
|
-
async kill(signal = "SIGTERM") {
|
|
4960
|
-
if (typeof window !== "undefined" && window.craft && this.processId) {
|
|
4961
|
-
await callBridge3("process.kill", { pid: this.processId, signal });
|
|
4962
|
-
if (this.exited)
|
|
4963
|
-
this.removeDomListeners();
|
|
4964
|
-
}
|
|
4965
|
-
}
|
|
4966
|
-
onStdout(handler) {
|
|
4967
|
-
this.onStdoutHandlers.push(handler);
|
|
4968
|
-
return () => {
|
|
4969
|
-
const idx = this.onStdoutHandlers.indexOf(handler);
|
|
4970
|
-
if (idx >= 0)
|
|
4971
|
-
this.onStdoutHandlers.splice(idx, 1);
|
|
4972
|
-
};
|
|
4973
|
-
}
|
|
4974
|
-
onStderr(handler) {
|
|
4975
|
-
this.onStderrHandlers.push(handler);
|
|
4976
|
-
return () => {
|
|
4977
|
-
const idx = this.onStderrHandlers.indexOf(handler);
|
|
4978
|
-
if (idx >= 0)
|
|
4979
|
-
this.onStderrHandlers.splice(idx, 1);
|
|
4980
|
-
};
|
|
4981
|
-
}
|
|
4982
|
-
onExit(handler) {
|
|
4983
|
-
this.onExitHandlers.push(handler);
|
|
4984
|
-
return () => {
|
|
4985
|
-
const idx = this.onExitHandlers.indexOf(handler);
|
|
4986
|
-
if (idx >= 0)
|
|
4987
|
-
this.onExitHandlers.splice(idx, 1);
|
|
4988
|
-
};
|
|
4989
|
-
}
|
|
4990
4978
|
}
|
|
4991
4979
|
function spawn2(command, args, options) {
|
|
4992
4980
|
return new ChildProcess(command, args, options);
|
|
@@ -4999,7 +4987,7 @@ function cwd() {
|
|
|
4999
4987
|
}
|
|
5000
4988
|
async function homeDir() {
|
|
5001
4989
|
if (typeof window !== "undefined" && window.craft) {
|
|
5002
|
-
return callBridge3("
|
|
4990
|
+
return (await callBridge3("fs.getHomeDir"))?.path || "/";
|
|
5003
4991
|
}
|
|
5004
4992
|
if (typeof process !== "undefined") {
|
|
5005
4993
|
const os = await import("os");
|
|
@@ -5009,7 +4997,7 @@ async function homeDir() {
|
|
|
5009
4997
|
}
|
|
5010
4998
|
async function tempDir() {
|
|
5011
4999
|
if (typeof window !== "undefined" && window.craft) {
|
|
5012
|
-
return callBridge3("
|
|
5000
|
+
return (await callBridge3("fs.getTempDir"))?.path || "/tmp";
|
|
5013
5001
|
}
|
|
5014
5002
|
if (typeof process !== "undefined") {
|
|
5015
5003
|
const os = await import("os");
|
|
@@ -5037,7 +5025,10 @@ function argv() {
|
|
|
5037
5025
|
}
|
|
5038
5026
|
async function open(target) {
|
|
5039
5027
|
if (typeof window !== "undefined" && window.craft) {
|
|
5040
|
-
|
|
5028
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\/]/i.test(target))
|
|
5029
|
+
await callBridge3("shell.openUrl", { url: target });
|
|
5030
|
+
else
|
|
5031
|
+
await shellFacade()?.openPath?.(target);
|
|
5041
5032
|
return;
|
|
5042
5033
|
}
|
|
5043
5034
|
const platform = getPlatform();
|
|
@@ -838,6 +838,23 @@ final class CraftChrome: ObservableObject {
|
|
|
838
838
|
@Published var tabs: [Tab] = []
|
|
839
839
|
@Published var tabAccessory: Tab?
|
|
840
840
|
private weak var tabAccessoryOwner: AnyObject?
|
|
841
|
+
|
|
842
|
+
/// Where the compact tab actions float over the page, in window
|
|
843
|
+
/// coordinates, and the shell that drew them; nil when nothing floats.
|
|
844
|
+
/// They cover the trailing end of whatever scrolls behind them, so a
|
|
845
|
+
/// screen gives its scroll views that much room at the end (#523): the
|
|
846
|
+
/// last row can be brought clear without the app knowing this geometry.
|
|
847
|
+
private(set) var floatingActions: CGRect?
|
|
848
|
+
private(set) weak var floatingActionsHost: UIView?
|
|
849
|
+
static let floatingActionsDidMove = Notification.Name("CraftFloatingActionsDidMove")
|
|
850
|
+
|
|
851
|
+
func floatingActionsMoved(to frame: CGRect?, in host: UIView?) {
|
|
852
|
+
let next = frame.flatMap { $0.isNull || $0.isEmpty ? nil : $0.integral }
|
|
853
|
+
guard next != floatingActions || (next != nil && host !== floatingActionsHost) else { return }
|
|
854
|
+
floatingActions = next
|
|
855
|
+
floatingActionsHost = next == nil ? nil : host
|
|
856
|
+
NotificationCenter.default.post(name: Self.floatingActionsDidMove, object: self)
|
|
857
|
+
}
|
|
841
858
|
@Published var selected: String?
|
|
842
859
|
/// The visible destination may be the accessory, independently of its owning stack.
|
|
843
860
|
@Published var visiblePath: String?
|
|
@@ -1665,6 +1665,8 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
|
|
|
1665
1665
|
override func viewDidLayoutSubviews() {
|
|
1666
1666
|
super.viewDidLayoutSubviews()
|
|
1667
1667
|
guard hybrid.controller === self else { return }
|
|
1668
|
+
// Once the actions are where this pass puts them.
|
|
1669
|
+
defer { CraftChrome.shared.floatingActionsMoved(to: floatingActionsFrame(), in: view) }
|
|
1668
1670
|
#if compiler(>=6.2)
|
|
1669
1671
|
if #available(iOS 26.0, *), accessoryModel?.iconOnly != true {
|
|
1670
1672
|
// UIKit already supplies the native safe area. This measurement is
|
|
@@ -1701,6 +1703,20 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
|
|
|
1701
1703
|
CraftChrome.shared.layoutChanged(tabBar.isHidden ? 0 : max(0, view.bounds.maxY - tabBar.frame.minY) + accessoryInset)
|
|
1702
1704
|
}
|
|
1703
1705
|
|
|
1706
|
+
/// The compact actions' frame in the window, or nil when none float over
|
|
1707
|
+
/// the page: a full-width shelf is reserved for through the safe area,
|
|
1708
|
+
/// and a hidden tab bar takes its actions with it. A group is its one
|
|
1709
|
+
/// capsule; separate actions are the row of them.
|
|
1710
|
+
private func floatingActionsFrame() -> CGRect? {
|
|
1711
|
+
guard let model = accessoryModel, model.iconOnly, view.window != nil,
|
|
1712
|
+
accessoryGlass.superview === view, !accessoryGlass.isHidden, !tabBar.isHidden else { return nil }
|
|
1713
|
+
var frame = accessoryGlass.frame
|
|
1714
|
+
if model.accessoryLayout == nil {
|
|
1715
|
+
for glass in companionGlasses where !glass.isHidden { frame = frame.union(glass.frame) }
|
|
1716
|
+
}
|
|
1717
|
+
return view.convert(frame, to: view.window)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1704
1720
|
/// One capsule above the trailing end of the tab bar, companions first
|
|
1705
1721
|
/// and the primary action last. Nothing divides the actions: the glass
|
|
1706
1722
|
/// is one control, and each action is found by its place and its symbol.
|