StikDebug/StikJIT/idevice/JITEnableContext.m
Stephen f3d2dec2e5
Semi-Rewrite – Major Refactor & Codebase Cleanup (#328)
* Change default UPLOAD_IPA value to 'false'

* Part 1 of the great debloat

* Remove Mini Tools, adopt native alerts in StikJITApp, and update HomeView UI

* Refactor views to use native SwiftUI alerts instead of CustomErrorView

* Update ProfileView to use minimal List UI style with native alerts

* Fix up homeview

* Squash: Redesign HomeView, remove Feature Flags, enable all tabs, refactor alerts, and updates

- Redesign HomeView to a simpler style with 'Enable JIT' action.
- Remove 'Help & Info' and 'Quick Launch' from Home.
- Fix DDI false positive by starting heartbeat on HomeView appear.
- Restore robust DDI checks and update app name to 'StikDebug'.
- Remove FeatureFlags and Beta tags; enable 'profiles', 'processes', 'location' tabs by default.
- Remove 'Devices' tab; add 'Target Device IP' field in Settings.
- Hardcode 'Settings' tab to 4th position.
- Refactor 'SettingsView' tab customization to be reorder-only.
- Remove 'Appearance' theme section from Settings.
- Refactor custom error cards to use native SwiftUI/UIKit alerts across the app (HomeView, ConsoleLogsView, ProfileView, etc).
- Restore JS callback support and script execution logic in HomeView.
- Add necessary Color extensions.
- Update project version to 3.0.0.

* Clean up: Remove In-App Purchases, update checker, and unused IPA installer code

* delete more bloat

* fix build issue

* Remove widget to save on App IDs

* Attempt heartbeat restart when Enable JIT button is pressed

* Get rid of PIP

* Update Tab Naming

* remove more bloat

* final changes for today

* Remove unused packages

* UX Improvements

* revert bundle id

* Update README.md

* remove old idevice

* add updated idevice

* Fix ddi false positive errors and add templates for GitHub stuff

* Remove more unused stuff.

* Fix links and update README content

* Update compatibility table in README.md

* Improve performance of console logs

* Remove old code

* Change default UPLOAD_IPA value to 'true'

* remove the welcome sheet

* import support for .mobiledevicepair

* Add back LC JIT stuff

* Fix heartbeat issues with LC JIT

* update the build yml for nightly stuff

* Update README.md

* Update README.md

* Update GitHub Actions workflow for IPA build

* Update GitHub Actions workflow for nightly builds

* Add URL scheme to kill tasks stikjit://kill-process?pid=1234

* fix build issues

* Added App Intents

* Unify the error handling style across the app
2026-02-25 13:44:00 -05:00

209 lines
6.5 KiB
Objective-C

//
// JITEnableContext.m
// StikJIT
//
// Created by s s on 2025/3/28.
//
#include "idevice.h"
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#include "heartbeat.h"
#include "jit.h"
#include "applist.h"
#include "profiles.h"
#include "JITEnableContext.h"
#import "StikDebug-Swift.h"
#include <os/lock.h>
#import <pthread.h>
static JITEnableContext* sharedJITContext = nil;
@implementation JITEnableContext {
int heartbeatToken;
NSError* lastHeartbeatError;
os_unfair_lock heartbeatLock;
BOOL heartbeatRunning;
dispatch_semaphore_t heartbeatSemaphore;
}
+ (instancetype)shared {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedJITContext = [[JITEnableContext alloc] init];
});
return sharedJITContext;
}
- (instancetype)init {
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL* logURL = [docPathUrl URLByAppendingPathComponent:@"idevice_log.txt"];
idevice_init_logger(Info, Debug, (char*)logURL.path.UTF8String);
syslogQueue = dispatch_queue_create("com.stik.syslogrelay.queue", DISPATCH_QUEUE_SERIAL);
syslogStreaming = NO;
syslogClient = NULL;
dispatch_queue_attr_t qosAttr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
processInspectorQueue = dispatch_queue_create("com.stikdebug.processInspector", qosAttr);
heartbeatToken = 0;
heartbeatLock = OS_UNFAIR_LOCK_INIT;
heartbeatRunning = NO;
heartbeatSemaphore = NULL;
lastHeartbeatError = nil;
return self;
}
- (NSError*)errorWithStr:(NSString*)str code:(int)code {
return [NSError errorWithDomain:@"StikJIT"
code:code
userInfo:@{ NSLocalizedDescriptionKey: str }];
}
- (LogFuncC)createCLogger:(LogFunc)logger {
return ^(const char* format, ...) {
va_list args;
va_start(args, format);
NSString* fmt = [NSString stringWithCString:format encoding:NSASCIIStringEncoding];
NSString* message = [[NSString alloc] initWithFormat:fmt arguments:args];
if ([message containsString:@"ERROR"] || [message containsString:@"Error"]) {
[[LogManagerBridge shared] addErrorLog:message];
} else if ([message containsString:@"WARNING"] || [message containsString:@"Warning"]) {
[[LogManagerBridge shared] addWarningLog:message];
} else if ([message containsString:@"DEBUG"]) {
[[LogManagerBridge shared] addDebugLog:message];
} else {
[[LogManagerBridge shared] addInfoLog:message];
}
if (logger) {
logger(message);
}
va_end(args);
};
}
- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error {
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL* pairingFileURL = [docPathUrl URLByAppendingPathComponent:@"pairingFile.plist"];
if (![fm fileExistsAtPath:pairingFileURL.path]) {
*error = [self errorWithStr:@"Pairing file not found!" code:-17];
return nil;
}
IdevicePairingFile* pairingFile = NULL;
IdeviceFfiError* err = idevice_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
if (err) {
*error = [self errorWithStr:@"Failed to read pairing file!" code:err->code];
return nil;
}
return pairingFile;
}
- (IdeviceProviderHandle*)getTcpProviderHandle {
return provider;
}
// only block until first heartbeat is completed or failed.
- (BOOL)startHeartbeat:(NSError**)err {
os_unfair_lock_lock(&heartbeatLock);
// If heartbeat is already running, wait for it to complete
if (heartbeatRunning) {
dispatch_semaphore_t waitSemaphore = heartbeatSemaphore;
os_unfair_lock_unlock(&heartbeatLock);
if (waitSemaphore) {
dispatch_semaphore_wait(waitSemaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(waitSemaphore);
}
*err = lastHeartbeatError;
return *err == nil;
}
// Mark heartbeat as running
heartbeatRunning = YES;
heartbeatSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_t completionSemaphore = heartbeatSemaphore;
os_unfair_lock_unlock(&heartbeatLock);
IdevicePairingFile* pairingFile = [self getPairingFileWithError:err];
if (*err) {
os_unfair_lock_lock(&heartbeatLock);
heartbeatRunning = NO;
heartbeatSemaphore = NULL;
os_unfair_lock_unlock(&heartbeatLock);
dispatch_semaphore_signal(completionSemaphore);
return NO;
}
globalHeartbeatToken++;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block bool completionCalled = false;
HeartbeatCompletionHandlerC Ccompletion = ^(int result, const char *message) {
if(completionCalled) {
return;
}
if (result != 0) {
*err = [self errorWithStr:[NSString stringWithCString:message
encoding:NSASCIIStringEncoding] code:result];
self->lastHeartbeatError = *err;
} else {
self->lastHeartbeatError = nil;
}
completionCalled = true;
dispatch_semaphore_signal(semaphore);
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
startHeartbeat(
pairingFile,
&self->provider,
globalHeartbeatToken,Ccompletion
);
});
// allow 5 seconds for heartbeat, otherwise we declare timeout
intptr_t isTimeout = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(5 * NSEC_PER_SEC)));
if(isTimeout) {
Ccompletion(-1, "Heartbeat failed to complete in reasonable time.");
}
os_unfair_lock_lock(&heartbeatLock);
heartbeatRunning = NO;
heartbeatSemaphore = NULL;
os_unfair_lock_unlock(&heartbeatLock);
dispatch_semaphore_signal(completionSemaphore);
return *err == nil;
}
- (BOOL)ensureHeartbeatWithError:(NSError**)err {
// if it's 15s after last heartbeat, we restart heartbeat.
if((!lastHeartbeatDate || [[NSDate now] timeIntervalSinceDate:lastHeartbeatDate] > 15)) {
return [self startHeartbeat:err];
}
return YES;
}
- (void)dealloc {
[self stopSyslogRelay];
if (provider) {
idevice_provider_free(provider);
}
}
@end