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