1 import UIKit
2 
3 struct ExpoDeviceType {
4   let modelName: String
5   let deviceYearClass: Int?
6 }
7 
8 public extension UIDevice {
9   // Credit: https://stackoverflow.com/a/26962452
10   static let modelIdentifier: String = {
11     var systemInfo = utsname()
12     uname(&systemInfo)
13     let machineMirror = Mirror(reflecting: systemInfo.machine)
14     let identifier = machineMirror.children.reduce("") { identifier, element in
15       guard let value = element.value as? Int8, value != 0 else {
16         return identifier
17       }
18       return identifier + String(UnicodeScalar(UInt8(value)))
19     }
20 
21     return identifier
22   }()
23 
24   // swiftlint:disable function_body_length closure_body_length
25   static internal let DeviceMap: ExpoDeviceType = {
mapToDevicenull26     func mapToDevice(identifier: String) -> ExpoDeviceType {
27       let currentYear = Calendar(identifier: .gregorian).dateComponents([.year], from: Date()).year
28 
29       #if os(iOS)
30       switch identifier {
31       case "iPod7,1":
32         return ExpoDeviceType(modelName: "iPod touch (6th generation)", deviceYearClass: 2015)
33       case "iPod9,1":
34         return ExpoDeviceType(modelName: "iPod touch (7th generation)", deviceYearClass: 2019)
35       case "iPhone6,1", "iPhone6,2":
36         return ExpoDeviceType(modelName: "iPhone 5s", deviceYearClass: 2013)
37       case "iPhone7,1":
38         return ExpoDeviceType(modelName: "iPhone 6 Plus", deviceYearClass: 2014)
39       case "iPhone7,2":
40         return ExpoDeviceType(modelName: "iPhone 6", deviceYearClass: 2014)
41       case "iPhone8,1":
42         return ExpoDeviceType(modelName: "iPhone 6s", deviceYearClass: 2015)
43       case "iPhone8,2":
44         return ExpoDeviceType(modelName: "iPhone 6s Plus", deviceYearClass: 2015)
45       case "iPhone9,1", "iPhone9,3":
46         return ExpoDeviceType(modelName: "iPhone 7", deviceYearClass: 2016)
47       case "iPhone9,2", "iPhone9,4":
48         return ExpoDeviceType(modelName: "iPhone 7 Plus", deviceYearClass: 2016)
49       case "iPhone10,1", "iPhone10,4":
50         return ExpoDeviceType(modelName: "iPhone 8", deviceYearClass: 2017)
51       case "iPhone10,2", "iPhone10,5":
52         return ExpoDeviceType(modelName: "iPhone 8 Plus", deviceYearClass: 2017)
53       case "iPhone10,3", "iPhone10,6":
54         return ExpoDeviceType(modelName: "iPhone X", deviceYearClass: 2017)
55       case "iPhone11,2":
56         return ExpoDeviceType(modelName: "iPhone XS", deviceYearClass: 2018)
57       case "iPhone11,4", "iPhone11,6":
58         return ExpoDeviceType(modelName: "iPhone XS Max", deviceYearClass: 2018)
59       case "iPhone11,8":
60         return ExpoDeviceType(modelName: "iPhone XR", deviceYearClass: 2018)
61       case "iPhone12,1":
62         return ExpoDeviceType(modelName: "iPhone 11", deviceYearClass: 2019)
63       case "iPhone12,3":
64         return ExpoDeviceType(modelName: "iPhone 11 Pro", deviceYearClass: 2019)
65       case "iPhone12,5":
66         return ExpoDeviceType(modelName: "iPhone 11 Pro Max", deviceYearClass: 2019)
67       case "iPhone13,1":
68         return ExpoDeviceType(modelName: "iPhone 12 mini", deviceYearClass: 2020)
69       case "iPhone13,2":
70         return ExpoDeviceType(modelName: "iPhone 12", deviceYearClass: 2020)
71       case "iPhone13,3":
72         return ExpoDeviceType(modelName: "iPhone 12 Pro", deviceYearClass: 2020)
73       case "iPhone13,4":
74         return ExpoDeviceType(modelName: "iPhone 12 Pro Max", deviceYearClass: 2020)
75       case "iPhone14,4":
76         return ExpoDeviceType(modelName: "iPhone 13 mini", deviceYearClass: 2021)
77       case "iPhone14,5":
78         return ExpoDeviceType(modelName: "iPhone 13", deviceYearClass: 2021)
79       case "iPhone14,2":
80         return ExpoDeviceType(modelName: "iPhone 13 Pro", deviceYearClass: 2021)
81       case "iPhone14,3":
82         return ExpoDeviceType(modelName: "iPhone 13 Pro Max", deviceYearClass: 2021)
83       case "iPhone14,7":
84         return ExpoDeviceType(modelName: "iPhone 14", deviceYearClass: 2022)
85       case "iPhone14,8":
86         return ExpoDeviceType(modelName: "iPhone 14 Plus", deviceYearClass: 2022)
87       case "iPhone15,2":
88         return ExpoDeviceType(modelName: "iPhone 14 Pro", deviceYearClass: 2022)
89       case "iPhone15,3":
90         return ExpoDeviceType(modelName: "iPhone 14 Pro Max", deviceYearClass: 2022)
91       case "iPhone8,4":
92         return ExpoDeviceType(modelName: "iPhone SE", deviceYearClass: 2016)
93       case "iPhone12,8":
94         return ExpoDeviceType(modelName: "iPhone SE (2nd generation)", deviceYearClass: 2020)
95       case "iPhone14,6":
96         return ExpoDeviceType(modelName: "iPhone SE (3rd generation)", deviceYearClass: 2022)
97       case "iPad6,11", "iPad6,12":
98         return ExpoDeviceType(modelName: "iPad (5th generation)", deviceYearClass: 2017)
99       case "iPad7,5", "iPad7,6":
100         return ExpoDeviceType(modelName: "iPad (6th generation)", deviceYearClass: 2018)
101       case "iPad7,11", "iPad7,12":
102         return ExpoDeviceType(modelName: "iPad (7th generation)", deviceYearClass: 2019)
103       case "iPad11,6", "iPad11,7":
104         return ExpoDeviceType(modelName: "iPad (8th generation)", deviceYearClass: 2020)
105       case "iPad12,1", "iPad12,2":
106         return ExpoDeviceType(modelName: "iPad (9th generation)", deviceYearClass: 2021)
107       case "iPad4,1", "iPad4,2", "iPad4,3":
108         return ExpoDeviceType(modelName: "iPad Air", deviceYearClass: 2013)
109       case "iPad5,3", "iPad5,4":
110         return ExpoDeviceType(modelName: "iPad Air 2", deviceYearClass: 2014)
111       case "iPad11,3", "iPad11,4":
112         return ExpoDeviceType(modelName: "iPad Air (3rd generation)", deviceYearClass: 2019)
113       case "iPad13,1", "iPad13,2":
114         return ExpoDeviceType(modelName: "iPad Air (4th generation)", deviceYearClass: 2020)
115       case "iPad13,16", "iPad13,17":
116         return ExpoDeviceType(modelName: "iPad Air (5th generation)", deviceYearClass: 2022)
117       case "iPad4,4", "iPad4,5", "iPad4,6":
118         return ExpoDeviceType(modelName: "iPad mini 2", deviceYearClass: 2013)
119       case "iPad4,7", "iPad4,8", "iPad4,9":
120         return ExpoDeviceType(modelName: "iPad mini 3", deviceYearClass: 2014)
121       case "iPad5,1", "iPad5,2":
122         return ExpoDeviceType(modelName: "iPad mini 4", deviceYearClass: 2015)
123       case "iPad11,1", "iPad11,2":
124         return ExpoDeviceType(modelName: "iPad mini (5th generation)", deviceYearClass: 2019)
125       case "iPad14,1", "iPad14,2":
126         return ExpoDeviceType(modelName: "iPad mini (6th generation)", deviceYearClass: 2021)
127       case "iPad6,3", "iPad6,4":
128         return ExpoDeviceType(modelName: "iPad Pro (9.7-inch)", deviceYearClass: 2016)
129       case "iPad7,3", "iPad7,4":
130         return ExpoDeviceType(modelName: "iPad Pro (10.5-inch)", deviceYearClass: 2017)
131       case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":
132         return ExpoDeviceType(modelName: "iPad Pro (11-inch) (1st generation)", deviceYearClass: 2018)
133       case "iPad8,9", "iPad8,10":
134         return ExpoDeviceType(modelName: "iPad Pro (11-inch) (2nd generation)", deviceYearClass: 2020)
135       case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7":
136         return ExpoDeviceType(modelName: "iPad Pro (11-inch) (3rd generation)", deviceYearClass: 2021)
137       case "iPad6,7", "iPad6,8":
138         return ExpoDeviceType(modelName: "iPad Pro (12.9-inch) (1st generation)", deviceYearClass: 2015)
139       case "iPad7,1", "iPad7,2":
140         return ExpoDeviceType(modelName: "iPad Pro (12.9-inch) (2nd generation)", deviceYearClass: 2017)
141       case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":
142         return ExpoDeviceType(modelName: "iPad Pro (12.9-inch) (3rd generation)", deviceYearClass: 2018)
143       case "iPad8,11", "iPad8,12":
144         return ExpoDeviceType(modelName: "iPad Pro (12.9-inch) (4th generation)", deviceYearClass: 2020)
145       case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11":
146         return ExpoDeviceType(modelName: "iPad Pro (12.9-inch) (5th generation)", deviceYearClass: 2021)
147       case "AppleTV5,3":
148         return ExpoDeviceType(modelName: "Apple TV HD (4th Generation, Siri)", deviceYearClass: 2015)
149       case "AppleTV6,2":
150         return ExpoDeviceType(modelName: "Apple TV 4K", deviceYearClass: 2017)
151       case "i386", "x86_64", "arm64":
152         return ExpoDeviceType(modelName: "Simulator iOS", deviceYearClass: currentYear)
153       default:
154         return ExpoDeviceType(modelName: identifier, deviceYearClass: currentYear)
155       }
156       #elseif os(tvOS)
157       switch identifier {
158       case "AppleTV5,3":
159         return ExpoDeviceType(modelName: "Apple TV HD (4th Generation, Siri)", deviceYearClass: 2015)
160       case "AppleTV6,2":
161         return ExpoDeviceType(modelName: "Apple TV 4K", deviceYearClass: 2017)
162       case "i386", "x86_64":
163         return ExpoDeviceType(modelName: "Simulator tvOS", deviceYearClass: currentYear)
164       default:
165         return identifier
166       }
167       #endif
168     }
169 
170     return mapToDevice(identifier: modelIdentifier)
171   }()
172   // swiftlint:enable function_body_length closure_body_length
173 
174   // Credit: https://github.com/developerinsider/isJailBroken/blob/master/IsJailBroken/Extension/UIDevice%2BJailBroken.swift
175   var isSimulator: Bool {
176     return TARGET_OS_SIMULATOR != 0
177   }
178 
179   var isJailbroken: Bool {
180     if UIDevice.current.isSimulator {
181       return false
182     }
183 
184     let jailbroken = JailbreakHelper.hasCydiaInstalled() || JailbreakHelper.doesContainSuspiciousApps() ||
185     JailbreakHelper.doesSuspiciousSystemPathExist() || JailbreakHelper.canEditSystemFiles()
186 
187     return jailbroken
188   }
189 }
190 
191 // Credit: https://github.com/developerinsider/isJailBroken/blob/master/IsJailBroken/Extension/UIDevice%2BJailBroken.swift
192 private struct JailbreakHelper {
hasCydiaInstallednull193   static func hasCydiaInstalled() -> Bool {
194     if let url = URL(string: "cydia://") {
195       return UIApplication.shared.canOpenURL(url)
196     }
197     return false
198   }
199 
doesContainSuspiciousAppsnull200   static func doesContainSuspiciousApps() -> Bool {
201     for path in suspiciousAppsPathToCheck where FileManager.default.fileExists(atPath: path) {
202       return true
203     }
204     return false
205   }
206 
doesSuspiciousSystemPathExistnull207   static func doesSuspiciousSystemPathExist() -> Bool {
208     for path in suspiciousSystemPathsToCheck where FileManager.default.fileExists(atPath: path) {
209       return true
210     }
211     return false
212   }
213 
canEditSystemFilesnull214   static func canEditSystemFiles() -> Bool {
215     let jailbreakText = "Developer Insider"
216     do {
217       try jailbreakText.write(toFile: jailbreakText, atomically: true, encoding: .utf8)
218       return true
219     } catch {
220       return false
221     }
222   }
223 
224   /**
225    Add more paths here to check for jail break
226    */
227   static var suspiciousAppsPathToCheck: [String] {
228     return [
229       "/Applications/Cydia.app",
230       "/Applications/blackra1n.app",
231       "/Applications/FakeCarrier.app",
232       "/Applications/Icy.app",
233       "/Applications/IntelliScreen.app",
234       "/Applications/MxTube.app",
235       "/Applications/RockApp.app",
236       "/Applications/SBSettings.app",
237       "/Applications/WinterBoard.app"
238     ]
239   }
240 
241   static var suspiciousSystemPathsToCheck: [String] {
242     return [
243       "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
244       "/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
245       "/private/var/lib/apt",
246       "/private/var/lib/apt/",
247       "/private/var/lib/cydia",
248       "/private/var/mobile/Library/SBSettings/Themes",
249       "/private/var/stash",
250       "/private/var/tmp/cydia.log",
251       "/System/Library/LaunchDaemons/com.ikey.bbot.plist",
252       "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
253       "/usr/bin/sshd",
254       "/usr/libexec/sftp-server",
255       "/usr/sbin/sshd",
256       "/etc/apt",
257       "/bin/bash",
258       "/Library/MobileSubstrate/MobileSubstrate.dylib"
259     ]
260   }
261 }
262