serve-sim 0.1.37 → 0.1.39
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/Sources/SimCameraInjector/SimCamFakes.h +2 -0
- package/Sources/SimCameraInjector/SimCamFakes.m +71 -2
- package/Sources/SimCameraInjector/SimCamSwizzles.m +42 -2
- package/bin/serve-sim-bin +0 -0
- package/dist/serve-sim.js +1 -1
- package/dist/simcam/libSimCameraInjector.dylib +0 -0
- package/package.json +1 -1
|
@@ -72,6 +72,8 @@ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p);
|
|
|
72
72
|
AVCaptureDeviceInput *SimCamFakeInputForPosition(AVCaptureDevicePosition p);
|
|
73
73
|
|
|
74
74
|
AVCaptureConnection *SimCamFakeConnectionForOutput(AVCaptureOutput *out);
|
|
75
|
+
void SimCamSetOutputInput(AVCaptureOutput *out, AVCaptureInput *input);
|
|
76
|
+
AVCaptureInput *SimCamOutputInput(AVCaptureOutput *out);
|
|
75
77
|
|
|
76
78
|
CMAttitude *SimCamSharedAttitude(void);
|
|
77
79
|
CMAccelerometerData *SimCamSharedAccelerometerData(void);
|
|
@@ -149,6 +149,8 @@ void SimCamLogSwallowedRuntimeError(NSString *via, id object, NSDictionary *user
|
|
|
149
149
|
- (NSArray *)supportedColorSpaces { return @[]; }
|
|
150
150
|
- (NSArray *)supportedDepthDataFormats { return @[]; }
|
|
151
151
|
- (BOOL)isPortraitEffectSupported { return NO; }
|
|
152
|
+
- (NSArray<Class> *)unsupportedCaptureOutputClasses { return @[]; }
|
|
153
|
+
- (BOOL)isStreamingDisparitySupported { return NO; }
|
|
152
154
|
- (float)minISO { return 25.0f; }
|
|
153
155
|
- (float)maxISO { return 6400.0f; }
|
|
154
156
|
- (CMTime)minExposureDuration { return CMTimeMake(1, 8000); }
|
|
@@ -286,6 +288,8 @@ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p) {
|
|
|
286
288
|
- (AVCaptureVideoOrientation)_videoOrientation;
|
|
287
289
|
@end
|
|
288
290
|
|
|
291
|
+
static AVCaptureInputPort *SimCamFakeInputPortForInput(AVCaptureInput *input, AVCaptureDevicePosition position);
|
|
292
|
+
|
|
289
293
|
@implementation SimCamFakeConnection {
|
|
290
294
|
__weak AVCaptureOutput *_outputRef;
|
|
291
295
|
AVCaptureDevicePosition _position;
|
|
@@ -311,8 +315,12 @@ AVCaptureDevice *SimCamFakeDeviceForPosition(AVCaptureDevicePosition p) {
|
|
|
311
315
|
return c;
|
|
312
316
|
}
|
|
313
317
|
- (AVCaptureOutput *)output { return _outputRef; }
|
|
314
|
-
- (NSArray *)inputPorts {
|
|
315
|
-
|
|
318
|
+
- (NSArray *)inputPorts {
|
|
319
|
+
AVCaptureInput *input = SimCamOutputInput(_outputRef) ?: SimCamFakeInputForPosition(_position);
|
|
320
|
+
AVCaptureInputPort *port = SimCamFakeInputPortForInput(input, _position);
|
|
321
|
+
return port ? @[port] : @[];
|
|
322
|
+
}
|
|
323
|
+
- (AVCaptureInput *)input { return SimCamOutputInput(_outputRef) ?: SimCamFakeInputForPosition(_position); }
|
|
316
324
|
- (AVCaptureVideoPreviewLayer *)videoPreviewLayer { return nil; }
|
|
317
325
|
- (BOOL)isEnabled { return _enabled; }
|
|
318
326
|
- (void)setEnabled:(BOOL)e { _enabled = e; }
|
|
@@ -411,6 +419,67 @@ AVCaptureConnection *SimCamFakeConnectionForOutput(AVCaptureOutput *out) {
|
|
|
411
419
|
return conn;
|
|
412
420
|
}
|
|
413
421
|
|
|
422
|
+
#pragma mark - SimCamFakeInputPort
|
|
423
|
+
|
|
424
|
+
@interface SimCamFakeInputPort : AVCaptureInputPort
|
|
425
|
+
@end
|
|
426
|
+
|
|
427
|
+
@implementation SimCamFakeInputPort {
|
|
428
|
+
__weak AVCaptureInput *_inputRef;
|
|
429
|
+
AVCaptureDevicePosition _position;
|
|
430
|
+
}
|
|
431
|
+
+ (instancetype)allocWithZone:(NSZone *)zone {
|
|
432
|
+
return class_createInstance([SimCamFakeInputPort class], 0);
|
|
433
|
+
}
|
|
434
|
+
+ (instancetype)portForInput:(AVCaptureInput *)input position:(AVCaptureDevicePosition)position {
|
|
435
|
+
SimCamFakeInputPort *p = [self alloc];
|
|
436
|
+
if (p) {
|
|
437
|
+
p->_inputRef = input;
|
|
438
|
+
p->_position = position;
|
|
439
|
+
}
|
|
440
|
+
return p;
|
|
441
|
+
}
|
|
442
|
+
- (AVCaptureInput *)input { return _inputRef; }
|
|
443
|
+
- (AVMediaType)mediaType { return AVMediaTypeVideo; }
|
|
444
|
+
- (AVCaptureDeviceType)sourceDeviceType { return AVCaptureDeviceTypeBuiltInWideAngleCamera; }
|
|
445
|
+
- (AVCaptureDevicePosition)sourceDevicePosition { return _position; }
|
|
446
|
+
- (CMFormatDescriptionRef)formatDescription { return SimCamSharedFakeFormat().formatDescription; }
|
|
447
|
+
- (BOOL)isEnabled { return YES; }
|
|
448
|
+
- (void)setEnabled:(BOOL)enabled { (void)enabled; }
|
|
449
|
+
@end
|
|
450
|
+
|
|
451
|
+
static char kSimCamOutputInputRefKey;
|
|
452
|
+
static char kSimCamFakeInputPortKey;
|
|
453
|
+
|
|
454
|
+
void SimCamSetOutputInput(AVCaptureOutput *out, AVCaptureInput *input) {
|
|
455
|
+
if (!out) return;
|
|
456
|
+
if (!input) {
|
|
457
|
+
objc_setAssociatedObject(out, &kSimCamOutputInputRefKey, nil, OBJC_ASSOCIATION_RETAIN);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
SimCamWeakRef *ref = [SimCamWeakRef new];
|
|
461
|
+
ref.target = input;
|
|
462
|
+
objc_setAssociatedObject(out, &kSimCamOutputInputRefKey, ref, OBJC_ASSOCIATION_RETAIN);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
AVCaptureInput *SimCamOutputInput(AVCaptureOutput *out) {
|
|
466
|
+
if (!out) return nil;
|
|
467
|
+
SimCamWeakRef *ref = objc_getAssociatedObject(out, &kSimCamOutputInputRefKey);
|
|
468
|
+
return ref.target;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static AVCaptureInputPort *SimCamFakeInputPortForInput(AVCaptureInput *input, AVCaptureDevicePosition position) {
|
|
472
|
+
if (!input) return nil;
|
|
473
|
+
AVCaptureInputPort *port = objc_getAssociatedObject(input, &kSimCamFakeInputPortKey);
|
|
474
|
+
if (!port) {
|
|
475
|
+
port = (AVCaptureInputPort *)[SimCamFakeInputPort portForInput:input position:position];
|
|
476
|
+
if (port) {
|
|
477
|
+
objc_setAssociatedObject(input, &kSimCamFakeInputPortKey, port, OBJC_ASSOCIATION_RETAIN);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return port;
|
|
481
|
+
}
|
|
482
|
+
|
|
414
483
|
#pragma mark - SimCamFakeInput marking
|
|
415
484
|
|
|
416
485
|
static char kSimCamFakeInputKey;
|
|
@@ -200,6 +200,7 @@ static BOOL SwizzleInstanceMethod(Class cls, SEL orig, SEL swiz) {
|
|
|
200
200
|
static char kSimCamSessionRunningKey;
|
|
201
201
|
static char kSimCamSessionInputsKey;
|
|
202
202
|
static char kSimCamSessionOutputsKey;
|
|
203
|
+
static char kSimCamOutputAttachedToFakeSessionKey;
|
|
203
204
|
|
|
204
205
|
static NSMutableArray *SimCamSessionTrackedInputs(AVCaptureSession *s) {
|
|
205
206
|
NSMutableArray *arr = objc_getAssociatedObject(s, &kSimCamSessionInputsKey);
|
|
@@ -218,6 +219,39 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
218
219
|
return arr;
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
static AVCaptureInput *SimCamFirstFakeInputForSession(AVCaptureSession *s) {
|
|
223
|
+
for (AVCaptureInput *candidate in SimCamSessionTrackedInputs(s)) {
|
|
224
|
+
if (SimCamIsFakeInput(candidate)) return candidate;
|
|
225
|
+
}
|
|
226
|
+
return nil;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Real AVFoundation only exposes output connections after an output has been
|
|
230
|
+
// attached to a session. Keep this per-output so newly created outputs still
|
|
231
|
+
// look disconnected during client-side session configuration checks.
|
|
232
|
+
static void SimCamMarkOutputAttachedToFakeSession(AVCaptureSession *s, AVCaptureOutput *output) {
|
|
233
|
+
if (!output) return;
|
|
234
|
+
objc_setAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey, @YES, OBJC_ASSOCIATION_RETAIN);
|
|
235
|
+
SimCamSetOutputInput(output, SimCamFirstFakeInputForSession(s));
|
|
236
|
+
}
|
|
237
|
+
static void SimCamUnmarkOutputAttachedToFakeSession(AVCaptureOutput *output) {
|
|
238
|
+
if (!output) return;
|
|
239
|
+
objc_setAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey, nil, OBJC_ASSOCIATION_RETAIN);
|
|
240
|
+
SimCamSetOutputInput(output, nil);
|
|
241
|
+
}
|
|
242
|
+
static BOOL SimCamOutputAttachedToFakeSession(AVCaptureOutput *output) {
|
|
243
|
+
if (!output) return NO;
|
|
244
|
+
return [objc_getAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey) boolValue];
|
|
245
|
+
}
|
|
246
|
+
static void SimCamRefreshAttachedOutputInputsForSession(AVCaptureSession *s) {
|
|
247
|
+
AVCaptureInput *input = SimCamFirstFakeInputForSession(s);
|
|
248
|
+
for (AVCaptureOutput *output in SimCamSessionTrackedOutputs(s)) {
|
|
249
|
+
if (SimCamOutputAttachedToFakeSession(output)) {
|
|
250
|
+
SimCamSetOutputInput(output, input);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
221
255
|
@interface AVCaptureSession (SimCam)
|
|
222
256
|
@end
|
|
223
257
|
@implementation AVCaptureSession (SimCam)
|
|
@@ -229,6 +263,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
229
263
|
SimCamMarkSessionUsingFakeCamera(self, YES);
|
|
230
264
|
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
231
265
|
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
266
|
+
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
232
267
|
simcam_log(@"addInput: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
233
268
|
p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
234
269
|
(unsigned long)tracked.count);
|
|
@@ -248,6 +283,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
248
283
|
SimCamMarkSessionUsingFakeCamera(self, YES);
|
|
249
284
|
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
250
285
|
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
286
|
+
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
251
287
|
simcam_log(@"addInputWithNoConnections: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
252
288
|
p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
253
289
|
(unsigned long)tracked.count);
|
|
@@ -259,6 +295,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
259
295
|
if (SimCamIsFakeInput(input)) {
|
|
260
296
|
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
261
297
|
[tracked removeObject:input];
|
|
298
|
+
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
262
299
|
if (tracked.count == 0) SimCamMarkSessionUsingFakeCamera(self, NO);
|
|
263
300
|
simcam_log(@"removeInput: fake input — untracked (count=%lu)",
|
|
264
301
|
(unsigned long)tracked.count);
|
|
@@ -268,6 +305,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
268
305
|
}
|
|
269
306
|
- (void)simcam_addOutput:(AVCaptureOutput *)output {
|
|
270
307
|
SimCamSetPosition(output, SimCamPositionOf(self));
|
|
308
|
+
SimCamMarkOutputAttachedToFakeSession(self, output);
|
|
271
309
|
SimCamMarkCameraInUse();
|
|
272
310
|
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
273
311
|
if (![tracked containsObject:output]) [tracked addObject:output];
|
|
@@ -279,6 +317,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
279
317
|
- (BOOL)simcam_canAddOutput:(AVCaptureOutput *)output { return YES; }
|
|
280
318
|
- (void)simcam_addOutputWithNoConnections:(AVCaptureOutput *)output {
|
|
281
319
|
SimCamSetPosition(output, SimCamPositionOf(self));
|
|
320
|
+
SimCamMarkOutputAttachedToFakeSession(self, output);
|
|
282
321
|
SimCamMarkCameraInUse();
|
|
283
322
|
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
284
323
|
if (![tracked containsObject:output]) [tracked addObject:output];
|
|
@@ -288,6 +327,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
288
327
|
(int)SimCamPositionOf(self));
|
|
289
328
|
}
|
|
290
329
|
- (void)simcam_removeOutput:(AVCaptureOutput *)output {
|
|
330
|
+
SimCamUnmarkOutputAttachedToFakeSession(output);
|
|
291
331
|
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
292
332
|
[tracked removeObject:output];
|
|
293
333
|
simcam_log(@"removeOutput: %@ — untracked (count=%lu)",
|
|
@@ -436,7 +476,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
436
476
|
- (AVCaptureConnection *)simcam_connectionWithMediaType:(AVMediaType)mediaType {
|
|
437
477
|
AVCaptureConnection *real = [self simcam_connectionWithMediaType:mediaType];
|
|
438
478
|
if (real) return real;
|
|
439
|
-
if (!
|
|
479
|
+
if (!SimCamOutputAttachedToFakeSession(self)) return nil;
|
|
440
480
|
if (![mediaType isEqualToString:AVMediaTypeVideo]) return nil;
|
|
441
481
|
AVCaptureConnection *fake = SimCamFakeConnectionForOutput(self);
|
|
442
482
|
simcam_log(@"connectionWithMediaType:%@ → fake %p for %@ %p",
|
|
@@ -446,7 +486,7 @@ static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
|
446
486
|
- (NSArray<AVCaptureConnection *> *)simcam_connections {
|
|
447
487
|
NSArray *real = [self simcam_connections];
|
|
448
488
|
if (real.count > 0) return real;
|
|
449
|
-
if (!
|
|
489
|
+
if (!SimCamOutputAttachedToFakeSession(self)) return real ?: @[];
|
|
450
490
|
AVCaptureConnection *fake = SimCamFakeConnectionForOutput(self);
|
|
451
491
|
return fake ? @[fake] : @[];
|
|
452
492
|
}
|
package/bin/serve-sim-bin
CHANGED
|
Binary file
|
package/dist/serve-sim.js
CHANGED
|
@@ -92,7 +92,7 @@ Usage:
|
|
|
92
92
|
serve-sim permissions reset <permission|all> <bundle-id> [-d <udid|name>]
|
|
93
93
|
serve-sim permissions list [bundle-id] [-d <udid|name>]
|
|
94
94
|
|
|
95
|
-
Permissions: ${xe().join(", ")}`),process.exit(1);let s=t.device?Y(t.device):C_();if(!s)console.error("No booted simulator. Boot one or pass -d <udid|name>."),process.exit(1);if(t.verb==="list"){let i={udid:s,bundleId:t.bundleId??null,tcc:Ti(s,t.bundleId),location:ci(s,t.bundleId),notifications:bi(s,t.bundleId)};console.log(JSON.stringify(i,null,e?0:2)),process.exit(0)}let C=t.bundleId;try{if(t.permission==="all")for(let i of xe())ft(s,"reset",i,void 0,C);else ft(s,t.verb,t.permission,t.value,C)}catch(i){console.error(i?.message??String(i)),process.exit(1)}if(e)console.log(JSON.stringify({udid:s,verb:t.verb,permission:t.permission,value:t.value??null,bundleId:C}));else{let i=t.value?` (${t.value})`:"";console.log(`\uD83D\uDD10 ${t.verb} ${t.permission}${i} for ${C} on ${s}`)}process.exit(0)}eS();var P_="./serve-sim-bin-
|
|
95
|
+
Permissions: ${xe().join(", ")}`),process.exit(1);let s=t.device?Y(t.device):C_();if(!s)console.error("No booted simulator. Boot one or pass -d <udid|name>."),process.exit(1);if(t.verb==="list"){let i={udid:s,bundleId:t.bundleId??null,tcc:Ti(s,t.bundleId),location:ci(s,t.bundleId),notifications:bi(s,t.bundleId)};console.log(JSON.stringify(i,null,e?0:2)),process.exit(0)}let C=t.bundleId;try{if(t.permission==="all")for(let i of xe())ft(s,"reset",i,void 0,C);else ft(s,t.verb,t.permission,t.value,C)}catch(i){console.error(i?.message??String(i)),process.exit(1)}if(e)console.log(JSON.stringify({udid:s,verb:t.verb,permission:t.permission,value:t.value??null,bundleId:C}));else{let i=t.value?` (${t.value})`:"";console.log(`\uD83D\uDD10 ${t.verb} ${t.permission}${i} for ${C} on ${s}`)}process.exit(0)}eS();var P_="./serve-sim-bin-tahdc0bd.";var D_=at(import.meta.url);function LS(){if(!X(r_))Ne(r_,{recursive:!0})}function t_(_){if(_)return NS(Ae(_));for(let e of ye()){let S=NS(e);if(S)return S}return null}var we={at:0,booted:null};function gy(){let _=Date.now();if(we.booted&&_-we.at<1000)return we.booted;try{let e=k("xcrun simctl list devices booted -j",{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3000}),S=JSON.parse(e),t=new Set;for(let s of Object.values(S.devices))for(let C of s)if(C.state==="Booted")t.add(C.udid);return we={at:_,booted:t},t}catch{return null}}function NS(_){try{if(!X(_))return o_("state file missing %s",_),null;let e=JSON.parse(S_(_,"utf-8"));try{process.kill(e.pid,0)}catch{return o_("helper pid %d dead, removing stale state %s",e.pid,_),U_(_),null}let S=gy();if(S&&!S.has(e.device)){o_("helper pid %d bound to non-booted device %s — killing stale helper",e.pid,e.device),console.error(`[serve-sim] Helper pid ${e.pid} is bound to device ${e.device} which is no longer booted — killing stale helper.`);try{process.kill(e.pid,"SIGTERM")}catch{}try{U_(_)}catch{}return null}return o_("state ok pid=%d device=%s port=%d",e.pid,e.device,e.port),e}catch(e){return o_("readStateFile threw for %s: %o",_,e),null}}function ge(){let _=[];for(let e of ye()){let S=NS(e);if(S)_.push(S)}return _}function ps(_){LS(),ue(Ae(_.device),JSON.stringify(_,null,2)),o_("wrote state pid=%d device=%s port=%d",_.pid,_.device,_.port)}function Se(_){if(_){o_("clearState device=%s",_);try{U_(Ae(_))}catch{}}else{o_("clearState (all)");for(let e of ye())try{U_(e)}catch{}}}function py(){let _=P_.startsWith("/$bunfs/");if(!_&&X(P_))return P_;if(!_){let C=i_(D_,"../bin/serve-sim-bin");if(X(C))return C;throw Error(`serve-sim-bin not found. Run 'bun run build:swift' first.
|
|
96
96
|
Checked: ${P_}, ${C}`)}let e=S_(P_),S=pS("sha256").update(e).digest("hex").slice(0,16),t=i_(ly(),"Library/Caches/serve-sim");Ne(t,{recursive:!0});let s=i_(t,`serve-sim-bin-${S}`);if(!X(s)){ue(s,e),Ny(s,493);try{k(`codesign -s - -f ${JSON.stringify(s)}`,{stdio:"ignore"})}catch{}}return s}function hs(){let _=null;try{_=k("xcode-select -p",{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{}if(!_)return process.env;let e=`${_}/Library/PrivateFrameworks`;return{...process.env,DYLD_FRAMEWORK_PATH:process.env.DYLD_FRAMEWORK_PATH?`${e}:${process.env.DYLD_FRAMEWORK_PATH}`:e}}function Ws(){try{let _=k("xcrun simctl list devices -j",{encoding:"utf-8"}),e=JSON.parse(_),S=Object.keys(e.devices).filter((t)=>/SimRuntime\.iOS-/i.test(t)).sort((t,s)=>{let C=(t.match(/iOS-(\d+)-(\d+)/)??[]).slice(1).map(Number),i=(s.match(/iOS-(\d+)-(\d+)/)??[]).slice(1).map(Number);return(i[0]??0)-(C[0]??0)||(i[1]??0)-(C[1]??0)});for(let t of S){let C=(e.devices[t]??[]).find((i)=>i.isAvailable!==!1&&/^iPhone\b/i.test(i.name));if(C)return{udid:C.udid,name:C.name}}}catch{}return null}function Ls(_){try{let e=k("xcrun simctl list devices -j",{encoding:"utf-8"}),S=JSON.parse(e);for(let t of Object.values(S.devices))for(let s of t)if(s.udid===_)return s.name}catch{}return null}function ds(_){try{let e=k("xcrun simctl list devices -j",{encoding:"utf-8"}),S=JSON.parse(e);for(let t of Object.values(S.devices))for(let s of t)if(s.udid===_)return s.state==="Booted"}catch{}return!1}function N_(_){try{return process.kill(_,0),!0}catch{return!1}}function VS(_){try{process.kill(_,"SIGTERM")}catch{return}let e=Date.now()+500;while(Date.now()<e)try{process.kill(_,0),L_(25)}catch{return}try{process.kill(_,"SIGKILL")}catch{}let S=Date.now()+500;while(Date.now()<S)try{process.kill(_,0),L_(25)}catch{return}}function Ly(_){try{let e=k(`lsof -ti tcp:${_}`,{encoding:"utf-8",stdio:"pipe"}).trim();if(!e)return[];let S=process.pid;return e.split(`
|
|
97
97
|
`).map((t)=>parseInt(t,10)).filter((t)=>Number.isFinite(t)&&t!==S)}catch{return[]}}function hy(_){let e=Ly(_);if(e.length===0)return;console.log(`\x1B[90mPort ${_} busy, killing holder pid(s): ${e.join(", ")}\x1B[0m`);for(let S of e)try{process.kill(S,"SIGKILL")}catch{}L_(100)}function Wy(_){if(!ds(_))try{k(`xcrun simctl boot ${_}`,{encoding:"utf-8",stdio:"pipe"})}catch(e){let S=(e.stderr??e.message??"").toLowerCase();if(!S.includes("booted")&&!S.includes("current state"))throw Error(`Failed to boot device ${_}: ${e.stderr||e.message}`)}try{k("open -ga Simulator",{encoding:"utf-8",stdio:"pipe",timeout:3000})}catch{}}function dy(){let _=uy();for(let e of Object.values(_))for(let S of e??[])if(S.family==="IPv4"&&!S.internal)return S.address;return null}async function Gs(_){let e=new Set(ge().map((S)=>S.port));for(let S=_;S<_+100;S++){if(e.has(S))continue;if(await Tt(S))return S}throw Error(`No available port found in range ${_}-${_+99}`)}async function Gy(_){Wy(_);try{k(`xcrun simctl bootstatus ${_} -b`,{encoding:"utf-8",stdio:"pipe",timeout:60000})}catch(e){if(!ds(_))console.error(`Device ${_} failed to reach booted state: ${e.stderr||e.message}`),process.exit(1)}}async function Ps(_,e,S,t){let s=!1,C=Date.now();j("waitForHelperReady pid=%d url=%s",_,e);for(let R=0;R<30;R++){if(!t()){j("helper pid=%d died during /health polling (attempt %d)",_,R);break}try{if((await fetch(`${e}/health`)).ok){s=!0,j("helper pid=%d /health ok after %dms",_,Date.now()-C);break}}catch{}await new Promise((y)=>setTimeout(y,100))}if(!s)j("helper pid=%d /health never responded (%dms)",_,Date.now()-C);if(s){let R=Date.now(),y=R+8000,A=!1;while(Date.now()<y){if(await new Promise(($)=>setTimeout($,200)),!t()){j("helper pid=%d died while awaiting Capture started",_),s=!1;break}try{if(S_(S,"utf-8").includes("Capture started")){A=!0,j("helper pid=%d saw 'Capture started' after %dms",_,Date.now()-R);break}}catch{}}if(s&&!A)j("helper pid=%d ready but never logged 'Capture started' within %dms — stream may not produce frames",_,Date.now()-R)}let i="";try{i=S_(S,"utf-8").trim()}catch{}return{ready:s,log:i}}async function Py(_){let{helperPath:e,udid:S,port:t,host:s,logFile:C}=_,i=`http://${s}:${t}`;LS();let R=Ve(C,"w"),y=gS(e,[S,"--port",String(t)],{detached:!0,stdio:["ignore",R,R],env:hs()});y.unref(),le(R);let A=y.pid,$=!1;y.once("exit",()=>{$=!0});let{ready:o,log:B}=await Ps(A,i,C,()=>!$&&N_(A));return{ready:o,pid:A,exited:$||!N_(A),log:B}}async function My(_){let{helperPath:e,udid:S,port:t,host:s,logFile:C}=_,i=`http://${s}:${t}`;LS();let R=Ve(C,"w"),y=gS(e,[S,"--port",String(t)],{detached:!1,stdio:["ignore",R,R],env:hs()});le(R);let A=y.pid,$=!1;y.once("exit",()=>{$=!0});let{ready:o,log:B}=await Ps(A,i,C,()=>!$&&N_(A));return{ready:o,child:y,log:B}}async function Ms(_,e,S){j("startHelper udid=%s port=%d detach=%s",_,e,S.detach),await Gy(_);let t="127.0.0.1",s=py(),C=e_(r_,`server-${_}.log`);j("helper binary=%s logFile=%s",s,C);let i={helperPath:s,udid:_,port:e,host:t,logFile:C},R="",y=2;for(let $=1;$<=y;$++){if(j("spawn attempt %d/%d",$,y),hy(e),S.detach){let o=await Py(i);if(j("spawnHelperDetached result ready=%s pid=%d exited=%s",o.ready,o.pid,o.exited),o.ready){let B={pid:o.pid,port:e,device:_,url:`http://${t}:${e}`,streamUrl:`http://${t}:${e}/stream.mjpeg`,wsUrl:`ws://${t}:${e}/ws`};return ps(B),{pid:o.pid}}VS(o.pid),R=o.log}else{let o=await My(i);if(j("spawnHelperAttached result ready=%s pid=%d",o.ready,o.child.pid),o.ready){let B={pid:o.child.pid,port:e,device:_,url:`http://${t}:${e}`,streamUrl:`http://${t}:${e}/stream.mjpeg`,wsUrl:`ws://${t}:${e}/ws`};return ps(B),{pid:o.child.pid,child:o.child}}VS(o.child.pid),R=o.log}if($<y)await new Promise((o)=>setTimeout(o,500))}let A=R?`Helper failed:
|
|
98
98
|
${R}`:"Helper process failed to start";console.error(A),process.exit(1)}async function Uy(_,e,S){_S("follow devices=%o startPort=%d",_,e);let t=_.length>0?_.map(Y):(()=>{let A=C_();if(A)return[A];let $=Ws();if(!$)console.error("No device specified and no available iOS simulator found."),process.exit(1);if(!S)console.log(`No booted simulator — booting ${$.name}...`);return[$.udid]})(),s=new Map,C=[],i=e;for(let A of t){let $=t_(A);if($){if(!S){let v=Ls(A)??A;if(t.length>1)console.log(`
|
|
Binary file
|