serve-sim 0.1.44-beta.99.3 → 0.1.44
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/Package.swift +7 -28
- package/Sources/SimNative/build.sh +18 -24
- package/dist/middleware.js +24 -24
- package/dist/native/serve-sim-native.node +0 -0
- package/dist/serve-sim.js +65 -65
- package/dist/simax/serve-sim-ax-settings +0 -0
- package/dist/simcam/libSimCameraInjector.dylib +0 -0
- package/dist/simcam/serve-sim-camera-helper +0 -0
- package/package.json +4 -7
- package/src/middleware.ts +66 -26
- /package/Sources/{SimStreamHelper → SimNative}/AccessibilityBridge.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/FrameCapture.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/H264Encoder.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/HIDInjector.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/SimFrameworks.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/StreamFormat.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/VideoEncoder.swift +0 -0
- /package/Sources/{SimStreamHelper → SimNative}/Xcode.swift +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serve-sim",
|
|
3
|
-
"version": "0.1.44
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Evan Bacon",
|
|
@@ -46,10 +46,8 @@
|
|
|
46
46
|
"Sources/SimAXSettings/build.sh",
|
|
47
47
|
"dist/native/serve-sim-native.node",
|
|
48
48
|
"Package.swift",
|
|
49
|
-
"Sources/SimNative
|
|
50
|
-
"Sources/SimNative/
|
|
51
|
-
"Sources/SimNative/build.sh",
|
|
52
|
-
"Sources/SimStreamHelper/*.swift"
|
|
49
|
+
"Sources/SimNative/*.swift",
|
|
50
|
+
"Sources/SimNative/build.sh"
|
|
53
51
|
],
|
|
54
52
|
"exports": {
|
|
55
53
|
"./middleware": {
|
|
@@ -76,11 +74,10 @@
|
|
|
76
74
|
"commander": "^14.0.1",
|
|
77
75
|
"debug": "^4.4.3",
|
|
78
76
|
"lucide-react": "^1.20.0",
|
|
79
|
-
"node-swift": "1.
|
|
77
|
+
"node-swift": "1.5.1",
|
|
80
78
|
"preact": "^10.29.1",
|
|
81
79
|
"react": "^19.0.0",
|
|
82
80
|
"react-dom": "^19.0.0",
|
|
83
|
-
"serve-sim-client": "workspace:*",
|
|
84
81
|
"tailwindcss": "^4.1.7",
|
|
85
82
|
"typescript": "^5.7.0"
|
|
86
83
|
},
|
package/src/middleware.ts
CHANGED
|
@@ -302,6 +302,29 @@ function queryDevice(rawUrl: string): string | null {
|
|
|
302
302
|
return new URLSearchParams(rawUrl.slice(qIndex + 1)).get("device");
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Parse `/grid/api` pagination params. `limit` absent → return the whole list
|
|
307
|
+
* (back-compat for embedded mounts that expect every device in one response).
|
|
308
|
+
* The full DeviceKit `chrome` descriptor is only resolved for the returned
|
|
309
|
+
* page, so a remote viewer over a tunnel fetches a small first page instead of
|
|
310
|
+
* the whole simulator catalog (~150KB) up front.
|
|
311
|
+
*/
|
|
312
|
+
export function parseGridPaging(rawUrl: string): { limit: number | null; offset: number } {
|
|
313
|
+
const qIndex = rawUrl.indexOf("?");
|
|
314
|
+
if (qIndex === -1) return { limit: null, offset: 0 };
|
|
315
|
+
const params = new URLSearchParams(rawUrl.slice(qIndex + 1));
|
|
316
|
+
const rawLimit = params.get("limit");
|
|
317
|
+
const rawOffset = params.get("offset");
|
|
318
|
+
// Clamp to sane bounds; ignore non-numeric/negative input rather than erroring.
|
|
319
|
+
const limit =
|
|
320
|
+
rawLimit == null || !/^\d+$/.test(rawLimit)
|
|
321
|
+
? null
|
|
322
|
+
: Math.min(Math.max(Number(rawLimit), 1), 1000);
|
|
323
|
+
const offset =
|
|
324
|
+
rawOffset == null || !/^\d+$/.test(rawOffset) ? 0 : Math.max(Number(rawOffset), 0);
|
|
325
|
+
return { limit, offset };
|
|
326
|
+
}
|
|
327
|
+
|
|
305
328
|
function hostForRequest(req: SimReq): string | undefined {
|
|
306
329
|
const host = req.headers?.host;
|
|
307
330
|
if (host) return host;
|
|
@@ -1263,32 +1286,14 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
1263
1286
|
const states = readServeSimStates();
|
|
1264
1287
|
const helperByUdid = new Map(states.map((s) => [s.device, s] as const));
|
|
1265
1288
|
const sims = listAllSimulators();
|
|
1266
|
-
const devices = sims.map((d) => {
|
|
1267
|
-
const helper = helperByUdid.get(d.udid);
|
|
1268
|
-
const remoteHelper = helper ? rewriteStateForRequestHost(helper, hostForRequest(req), base, httpProtocolForRequest(req), proxyHelpers) : null;
|
|
1269
|
-
return {
|
|
1270
|
-
device: d.udid,
|
|
1271
|
-
name: d.name,
|
|
1272
|
-
runtime: d.runtime,
|
|
1273
|
-
state: d.state,
|
|
1274
|
-
chrome: resolveDeviceKitChrome(d),
|
|
1275
|
-
placeholderAsset: resolveDevicePlaceholderAsset(d),
|
|
1276
|
-
helper: remoteHelper
|
|
1277
|
-
? {
|
|
1278
|
-
port: remoteHelper.port,
|
|
1279
|
-
url: remoteHelper.url,
|
|
1280
|
-
streamUrl: remoteHelper.streamUrl,
|
|
1281
|
-
wsUrl: remoteHelper.wsUrl,
|
|
1282
|
-
}
|
|
1283
|
-
: null,
|
|
1284
|
-
};
|
|
1285
|
-
});
|
|
1286
1289
|
// Order mirrors Xcode's Devices window: the devices the user is actually
|
|
1287
1290
|
// using float to the top — streaming first, then booted, then the
|
|
1288
1291
|
// simulator they last opened in Simulator.app — and everything else falls
|
|
1289
1292
|
// back to a stable family / newest-OS / name grouping. This surfaces the
|
|
1290
1293
|
// handful of relevant devices instead of burying them in an alphabetical
|
|
1291
|
-
// wall of near-identical names.
|
|
1294
|
+
// wall of near-identical names. Sort on the cheap metadata BEFORE
|
|
1295
|
+
// resolving the DeviceKit chrome descriptor, so pagination resolves chrome
|
|
1296
|
+
// only for the page actually returned.
|
|
1292
1297
|
const preferredUdid = getPreferredDeviceUdid();
|
|
1293
1298
|
const familyRank = (name: string): number => {
|
|
1294
1299
|
if (/iphone/i.test(name)) return 0;
|
|
@@ -1298,9 +1303,18 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
1298
1303
|
if (/vision|reality/i.test(name)) return 4;
|
|
1299
1304
|
return 5;
|
|
1300
1305
|
};
|
|
1301
|
-
// Lower is higher in the list: streaming > booted > last-opened
|
|
1302
|
-
|
|
1303
|
-
|
|
1306
|
+
// Lower is higher in the list: streaming > selected > booted > last-opened
|
|
1307
|
+
// > rest. The active `?device=` selection is ranked near the top so it's
|
|
1308
|
+
// always inside the first page — otherwise a paginated client that selected
|
|
1309
|
+
// a shut-down device deep in the catalog would get no chrome/placeholder
|
|
1310
|
+
// for the view it's actually showing.
|
|
1311
|
+
const stateRank = (d: (typeof sims)[number]) => {
|
|
1312
|
+
if (helperByUdid.has(d.udid)) return 0;
|
|
1313
|
+
if (selectedDevice && d.udid === selectedDevice) return 1;
|
|
1314
|
+
if (d.state === "Booted") return 2;
|
|
1315
|
+
if (d.udid === preferredUdid) return 3;
|
|
1316
|
+
return 4;
|
|
1317
|
+
};
|
|
1304
1318
|
// Newest runtime first, so "iPhone 17 Pro (27.0)" sorts above its 26.x twins.
|
|
1305
1319
|
const runtimeRank = (runtime: string): number => {
|
|
1306
1320
|
const m = runtime.match(/-(\d+)-(\d+)/);
|
|
@@ -1308,17 +1322,43 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
1308
1322
|
const minor = m ? Number(m[2]) : 0;
|
|
1309
1323
|
return -(major * 1000 + minor);
|
|
1310
1324
|
};
|
|
1311
|
-
|
|
1325
|
+
sims.sort((a, b) =>
|
|
1312
1326
|
stateRank(a) - stateRank(b) ||
|
|
1313
1327
|
familyRank(a.name) - familyRank(b.name) ||
|
|
1314
1328
|
a.name.localeCompare(b.name) ||
|
|
1315
1329
|
runtimeRank(a.runtime) - runtimeRank(b.runtime),
|
|
1316
1330
|
);
|
|
1331
|
+
|
|
1332
|
+
const total = sims.length;
|
|
1333
|
+
const { limit, offset } = parseGridPaging(rawUrl);
|
|
1334
|
+
const page = limit == null ? sims : sims.slice(offset, offset + limit);
|
|
1335
|
+
const devices = page.map((d) => {
|
|
1336
|
+
const helper = helperByUdid.get(d.udid);
|
|
1337
|
+
const remoteHelper = helper ? rewriteStateForRequestHost(helper, hostForRequest(req), base, httpProtocolForRequest(req), proxyHelpers) : null;
|
|
1338
|
+
return {
|
|
1339
|
+
device: d.udid,
|
|
1340
|
+
name: d.name,
|
|
1341
|
+
runtime: d.runtime,
|
|
1342
|
+
state: d.state,
|
|
1343
|
+
chrome: resolveDeviceKitChrome(d),
|
|
1344
|
+
placeholderAsset: resolveDevicePlaceholderAsset(d),
|
|
1345
|
+
helper: remoteHelper
|
|
1346
|
+
? {
|
|
1347
|
+
port: remoteHelper.port,
|
|
1348
|
+
url: remoteHelper.url,
|
|
1349
|
+
streamUrl: remoteHelper.streamUrl,
|
|
1350
|
+
wsUrl: remoteHelper.wsUrl,
|
|
1351
|
+
}
|
|
1352
|
+
: null,
|
|
1353
|
+
};
|
|
1354
|
+
});
|
|
1317
1355
|
res.writeHead(200, {
|
|
1318
1356
|
"Content-Type": "application/json",
|
|
1319
1357
|
"Cache-Control": "no-store",
|
|
1320
1358
|
});
|
|
1321
|
-
|
|
1359
|
+
// `total` lets the client show "X of Y" and know when to stop paging;
|
|
1360
|
+
// older clients that read only `devices` are unaffected.
|
|
1361
|
+
res.end(JSON.stringify({ devices, total, offset: limit == null ? 0 : offset, limit: limit ?? total }));
|
|
1322
1362
|
return;
|
|
1323
1363
|
}
|
|
1324
1364
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|