@@ -9,10 +9,11 @@ import SwiftUI
99import UniformTypeIdentifiers
1010import Pipify
1111
12- extension UIDocumentPickerViewController {
13- @objc func fix_init( forOpeningContentTypes contentTypes: [ UTType ] , asCopy: Bool ) -> UIDocumentPickerViewController {
14- return fix_init ( forOpeningContentTypes: contentTypes, asCopy: true )
15- }
12+ struct JITEnableConfiguration {
13+ var bundleID : String ? = nil
14+ var pid : Int ? = nil
15+ var scriptData : Data ? = nil
16+ var scriptName : String ? = nil
1617}
1718
1819struct HomeView : View {
@@ -37,13 +38,13 @@ struct HomeView: View {
3738 @State private var pidStr = " "
3839
3940 @State private var viewDidAppeared = false
40- @State private var pendingBundleIdToEnableJIT : String ? = nil
41- @State private var pendingPIDToEnableJIT : Int ? = nil
41+ @State private var pendingJITEnableConfiguration : JITEnableConfiguration ? = nil
4242 @AppStorage ( " enableAdvancedOptions " ) private var enableAdvancedOptions = false
4343
4444 @AppStorage ( " useDefaultScript " ) private var useDefaultScript = false
4545 @AppStorage ( " enablePiP " ) private var enablePiP = true
4646 @State var scriptViewShow = false
47+ @State var pipRequired = false
4748 @AppStorage ( " DefaultScriptName " ) var selectedScript = " attachDetach.js "
4849 @State var jsModel : RunJSViewModel ?
4950
@@ -301,12 +302,12 @@ struct HomeView: View {
301302 bundleID = selectedBundle
302303 isShowingInstalledApps = false
303304 HapticFeedbackHelper . trigger ( )
304- startJITInBackground ( with : selectedBundle)
305+ startJITInBackground ( bundleID : selectedBundle)
305306 }
306307 }
307308 . pipify ( isPresented: Binding (
308- get: { useDefaultScript && enablePiP && isProcessing } ,
309- set: { newValue in isProcessing = newValue }
309+ get: { pipRequired && enablePiP } ,
310+ set: { newValue in pipRequired = newValue }
310311 ) ) {
311312 RunJSViewPiP ( model: $jsModel)
312313 }
@@ -318,7 +319,6 @@ struct HomeView: View {
318319 ToolbarItem ( placement: . topBarTrailing) {
319320 Button ( " Done " ) {
320321 scriptViewShow = false
321- isProcessing = false
322322 }
323323 }
324324 }
@@ -348,7 +348,7 @@ struct HomeView: View {
348348 showAlert ( title: " " , message: " Invalid PID " . localized, showOk: true , completion: { _ in } )
349349 return
350350 }
351- startJITInBackground ( with : pid)
351+ startJITInBackground ( pid : pid)
352352
353353 } ,
354354 actionCancel: { _ in
@@ -361,31 +361,37 @@ struct HomeView: View {
361361 return
362362 }
363363
364+ var config = JITEnableConfiguration ( )
364365 let components = URLComponents ( url: url, resolvingAgainstBaseURL: false )
366+
367+ if let pidStr = components? . queryItems? . first ( where: { $0. name == " pid " } ) ? . value, let pid = Int ( pidStr) {
368+ config. pid = pid
369+ }
365370 if let bundleId = components? . queryItems? . first ( where: { $0. name == " bundle-id " } ) ? . value {
366- if viewDidAppeared {
367- startJITInBackground ( with: bundleId)
368- } else {
369- pendingBundleIdToEnableJIT = bundleId
370- }
371- } else if let pidStr = components? . queryItems? . first ( where: { $0. name == " pid " } ) ? . value, let pid = Int ( pidStr) {
372- if viewDidAppeared {
373- startJITInBackground ( with: pid)
374- } else {
375- pendingPIDToEnableJIT = pid
371+ config. bundleID = bundleId
372+ }
373+ if let scriptBase64URL = components? . queryItems? . first ( where: { $0. name == " script-data " } ) ? . value? . removingPercentEncoding {
374+ let base64 = base64URLToBase64 ( scriptBase64URL)
375+ if let scriptData = Data ( base64Encoded: base64) {
376+ config. scriptData = scriptData
376377 }
377378 }
379+ if let scriptName = components? . queryItems? . first ( where: { $0. name == " script-name " } ) ? . value {
380+ config. scriptName = scriptName
381+ }
382+
383+ if viewDidAppeared {
384+ startJITInBackground ( bundleID: config. bundleID, pid: config. pid, scriptData: config. scriptData, scriptName: config. scriptName, triggeredByURLScheme: true )
385+ } else {
386+ pendingJITEnableConfiguration = config
387+ }
378388
379389 }
380390 . onAppear ( ) {
381391 viewDidAppeared = true
382- if let pendingBundleIdToEnableJIT {
383- startJITInBackground ( with: pendingBundleIdToEnableJIT)
384- self . pendingBundleIdToEnableJIT = nil
385- }
386- if let pendingPIDToEnableJIT {
387- startJITInBackground ( with: pendingPIDToEnableJIT)
388- self . pendingPIDToEnableJIT = nil
392+ if let config = pendingJITEnableConfiguration {
393+ startJITInBackground ( bundleID: config. bundleID, pid: config. pid, scriptData: config. scriptData, scriptName: config. scriptName, triggeredByURLScheme: true )
394+ self . pendingJITEnableConfiguration = nil
389395 }
390396 }
391397 }
@@ -410,84 +416,113 @@ struct HomeView: View {
410416 // but we'll keep it empty to avoid breaking anything
411417 }
412418
413- private func getJsCallback( for scriptName: String ? = nil ) -> DebugAppCallback ? {
414- let name = scriptName ?? selectedScript
415- let selectedScriptURL = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) [ 0 ]
416- . appendingPathComponent ( " scripts " ) . appendingPathComponent ( name)
417-
418- if !FileManager. default. fileExists ( atPath: selectedScriptURL. path) {
419- return nil
420- }
421-
419+ private func getJsCallback( _ script: Data , name: String ? = nil ) -> DebugAppCallback {
422420 return { pid, debugProxyHandle, semaphore in
423421 jsModel = RunJSViewModel ( pid: Int ( pid) , debugProxy: debugProxyHandle, semaphore: semaphore)
424422 scriptViewShow = true
423+
425424 DispatchQueue . global ( qos: . background) . async {
426425 do {
427- try jsModel? . runScript ( path: selectedScriptURL)
428- isProcessing = false
426+ try jsModel? . runScript ( data: script, name: name)
429427 } catch {
430428 showAlert ( title: " Error Occurred While Executing the Default Script. " . localized, message: error. localizedDescription, showOk: true )
431429 }
432430 }
433431 }
434432 }
435433
436- private func startJITInBackground( with bundleID: String ) {
434+ // launch app following this order: pid > bundleID
435+ // load script following this order: scriptData > script file from script name > saved script for bundleID > default script
436+ // if advanced mode is disabled the whole script loading will be skipped. If use default script is disabled default script will not be loaded
437+ private func startJITInBackground( bundleID: String ? = nil , pid : Int ? = nil , scriptData: Data ? = nil , scriptName : String ? = nil , triggeredByURLScheme: Bool = false ) {
437438 isProcessing = true
438-
439439 // Add log message
440- LogManager . shared. addInfoLog ( " Starting Debug for \( bundleID) " )
440+ LogManager . shared. addInfoLog ( " Starting Debug for \( bundleID ?? String ( pid ?? 0 ) ) " )
441441
442442 DispatchQueue . global ( qos: . background) . async {
443- var callback : DebugAppCallback ? = nil
444- if enableAdvancedOptions {
445- let mapping = UserDefaults . standard. dictionary ( forKey: " BundleScriptMap " ) as? [ String : String ]
446- if let script = mapping ? [ bundleID] {
447- callback = getJsCallback ( for: script)
448- } else if useDefaultScript {
449- callback = getJsCallback ( )
443+ var scriptData = scriptData
444+ var scriptName = scriptName
445+ if enableAdvancedOptions && scriptData == nil {
446+ if scriptName == nil , let bundleID, let mapping = UserDefaults . standard. dictionary ( forKey: " BundleScriptMap " ) as? [ String : String ] {
447+ scriptName = mapping [ bundleID]
450448 }
449+
450+ if useDefaultScript && scriptName == nil {
451+ scriptName = selectedScript
452+ }
453+
454+ if scriptData == nil , let scriptName {
455+ let selectedScriptURL = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) [ 0 ]
456+ . appendingPathComponent ( " scripts " ) . appendingPathComponent ( scriptName)
457+
458+ if FileManager . default. fileExists ( atPath: selectedScriptURL. path) {
459+ do {
460+ scriptData = try Data ( contentsOf: selectedScriptURL)
461+ } catch {
462+ print ( " failed to load data from script \( error) " )
463+ }
464+
465+ }
466+ }
467+ } else {
468+ scriptData = nil
451469 }
452- let success = JITEnableContext . shared. debugApp ( withBundleID: bundleID, logger: { message in
470+
471+
472+ var callback : DebugAppCallback ? = nil
473+
474+ if let scriptData {
475+ callback = getJsCallback ( scriptData, name: scriptName ?? bundleID ?? " Script " )
476+ if triggeredByURLScheme {
477+ usleep ( 500000 )
478+ }
453479
480+ pipRequired = true
481+ }
482+
483+ let logger : LogFunc = { message in
484+
454485 if let message = message {
455486 // Log messages from the JIT process
456487 LogManager . shared. addInfoLog ( message)
457488 }
458- } , jsCallback: callback)
489+ }
490+ var success : Bool
491+ if let pid {
492+ success = JITEnableContext . shared. debugApp ( withPID: Int32 ( pid) , logger: logger, jsCallback: callback)
493+ } else if let bundleID {
494+ success = JITEnableContext . shared. debugApp ( withBundleID: bundleID, logger: logger, jsCallback: callback)
495+ } else {
496+ DispatchQueue . main. async {
497+ showAlert ( title: " Failed to Debug App " . localized, message: " Either bundle ID or PID should be specified. " . localized, showOk: true )
498+ }
499+ success = false
500+ }
459501
460- DispatchQueue . main. async {
461- LogManager . shared. addInfoLog ( " Debug process completed for \( bundleID) " )
462- isProcessing = false
502+ if success {
503+ DispatchQueue . main. async {
504+ LogManager . shared. addInfoLog ( " Debug process completed for \( bundleID ?? String ( pid ?? 0 ) ) " )
505+ }
463506 }
507+ isProcessing = false
508+ pipRequired = false
464509 }
465510 }
466511
467- private func startJITInBackground( with pid: Int ) {
468- isProcessing = true
469-
470- // Add log message
471- LogManager . shared. addInfoLog ( " Starting JIT for pid \( pid) " )
472-
473- DispatchQueue . global ( qos: . background) . async {
512+ func base64URLToBase64( _ base64url: String ) -> String {
513+ var base64 = base64url
514+ . replacingOccurrences ( of: " - " , with: " + " )
515+ . replacingOccurrences ( of: " _ " , with: " / " )
474516
475- let jsCallback : DebugAppCallback ? = ( enableAdvancedOptions && useDefaultScript) ? getJsCallback ( ) : nil
476- let success = JITEnableContext . shared. debugApp ( withPID: Int32 ( pid) , logger: { message in
477-
478- if let message = message {
479- // Log messages from the JIT process
480- LogManager . shared. addInfoLog ( message)
481- }
482- } , jsCallback: jsCallback)
483-
484- DispatchQueue . main. async {
485- LogManager . shared. addInfoLog ( " JIT process completed for \( pid) " )
486- showAlert ( title: " Success " . localized, message: String ( format: " JIT has been enabled for pid %d. " . localized, pid) , showOk: true , messageType: . success)
487- isProcessing = false
488- }
517+ // Pad with "=" to make length a multiple of 4
518+ let paddingLength = 4 - ( base64. count % 4 )
519+ if paddingLength < 4 {
520+ base64 += String ( repeating: " = " , count: paddingLength)
489521 }
522+
523+ return base64
490524 }
525+
491526}
492527
493528class InstalledAppsViewModel : ObservableObject {
0 commit comments