feat(ios): add Ios source (#1640)

* feat: add ios and rn source

* fix(ios): remove testing keys

* fix(tracker): change default path
This commit is contained in:
Delirium 2023-11-10 10:32:55 +01:00 committed by GitHub
parent 275c5fd136
commit c0da34c528
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
130 changed files with 7521 additions and 0 deletions

3
tracker/tracker-ios/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.build
.swiftpm
.DS_Store

View file

@ -0,0 +1,44 @@
Elastic License 2.0 (ELv2)
**Acceptance**
By using the software, you agree to all of the terms and conditions below.
**Copyright License**
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below
**Limitations**
You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.
**Patents**
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
**Notices**
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
**No Other Rights**
These terms do not imply any licenses other than those expressly granted in these terms.
**Termination**
If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
**No Liability**
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
**Definitions**
The *licensor* is the entity offering these terms, and the *software* is the software the licensor makes available under these terms, including any portion of it.
*you* refers to the individual or entity agreeing to these terms.
*your company* is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. *control* means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
*your licenses* are all the licenses granted to you for the software under these terms.
*use* means anything you do with the software requiring one of your licenses.
*trademark* means trademarks, service marks, and similar rights.

View file

@ -0,0 +1,14 @@
Pod::Spec.new do |s|
s.name = 'ORTracker'
s.version = '0.1.0'
s.summary = 'A short description of ORTracker.'
s.homepage = 'https://github.com/openreplay/openreplay/tracker/tracker-ios'
s.license = { :type => 'ELv2', :file => 'LICENSE.md' }
s.author = { 'Nick Delirium' => 'nick.delirium@proton.me' }
s.source = { :git => 'https://github.com/openreplay/openreplay/tracker/tracker-ios', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
s.swift_version = '5.0'
s.source_files = 'Sources/ORTracker/**/*'
s.dependency 'SWCompression'
s.dependency 'DeviceKit'
end

View file

@ -0,0 +1,32 @@
{
"pins" : [
{
"identity" : "bitbytedata",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tsolomko/BitByteData",
"state" : {
"revision" : "36df26fe4586b4f23d76cfd8b47076998343a2b2",
"version" : "2.0.3"
}
},
{
"identity" : "devicekit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/devicekit/DeviceKit.git",
"state" : {
"revision" : "d37e70cb2646666dcf276d7d3d4a9760a41ff8a6",
"version" : "4.9.0"
}
},
{
"identity" : "swcompression",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tsolomko/SWCompression.git",
"state" : {
"revision" : "cd39ca0a3b269173bab06f68b182b72fa690765c",
"version" : "4.8.5"
}
}
],
"version" : 2
}

View file

@ -0,0 +1,38 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ORTracker",
platforms: [
.iOS(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "ORTracker",
targets: ["ORTracker"]
),
],
dependencies: [
.package(url: "https://github.com/devicekit/DeviceKit.git", from: "4.0.0"),
.package(url: "https://github.com/tsolomko/SWCompression.git", .upToNextMajor(from: "4.8.5")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ORTracker",
dependencies: [
.product(name: "SWCompression", package: "SWCompression"),
.product(name: "DeviceKit", package: "DeviceKit"),
]
),
.testTarget(
name: "ORTrackerTests",
dependencies: ["ORTracker"]
),
]
)

View file

@ -0,0 +1,8 @@
platform :ios, '13.0'
target 'ORTracker' do
use_frameworks!
pod 'SWCompression', '~> 4.8'
pod 'DeviceKit', '~> 5.1'
end

View file

@ -0,0 +1,89 @@
Setting up tracker
```swift
// AppDelegate.swift
import ORTracker
//...
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ORTracker.shared.serverURL = "https://your.instance.com/ingest"
ORTracker.shared.start(projectKey: "projectkey", options: .defaults)
// ...
return true
}
```
Options (default all `true`)
```swift
let crashes: Bool
let analytics: Bool
let performances: Bool
let logs: Bool
let screen: Bool
let wifiOnly: Bool
```
Setting up touches listener
```swift
// SceneDelegate.Swift
import ORTracker
// ...
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
.environmentObject(TodoStore())
if let windowScene = scene as? UIWindowScene {
let window = TouchTrackingWindow(windowScene: windowScene) // <<<< here
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
```
Adding sensitive views (will be blurred in replay)
```swift
import ORTracker
// swiftUI
Text("Very important sensitive text")
.sensitive()
// UIKit
ORTracker.shared.addIgnoredView(view)
```
Adding tracked inputs
```swift
// swiftUI
TextField("Input", text: $text)
.observeInput(text: $text, label: "tracker input #1", masked: Bool)
// UIKit will use placeholder as label and sender.isSecureTextEntry to mask the input
Analytics.shared.addObservedInput(inputEl)
```
Observing views
```swift
// swiftUI
TextField("Test")
.observeView(title: "Screen title", viewName: "test input name")
// UIKit
Analytics.shared.addObservedView(view: inputEl, title: "Screen title", viewName: "test input name")
```
will send IOSScreenEnter and IOSScreenLeave when view appears/dissapears on/from screen

View file

@ -0,0 +1,230 @@
import UIKit
import CommonCrypto
protocol NSObjectCoding: NSCoding, NSObject {}
extension NSObjectCoding {
static func from(data: Data, offset: inout Int) throws -> Self {
let valueData = try data.readData(offset: &offset)
guard let result = try NSKeyedUnarchiver.unarchivedObject(ofClass: self, from: valueData)
else { throw NSError(domain: "ErrorDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error reading NSCoding"]) }
return result
}
}
extension Data {
mutating func appendString(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
func sha256() -> String {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &hash)
}
return Data(hash).hexEncodedString()
}
func hexEncodedString() -> String {
return map { String(format: "%02hhx", $0) }.joined()
}
func subdata(start: Int, length: Int) -> Data? {
let start = startIndex.advanced(by: start)
let end = start.advanced(by: length)
guard start >= 0, end <= count else { return nil }
return subdata(in: start..<end)
}
}
extension Data {
func readPrimary<T>(offset: inout Int) throws -> T {
if T.self == CGFloat.self {
return CGFloat(try readPrimary(offset: &offset) as Double) as! T
}
if T.self == UInt64.self {
return try readUint(offset: &offset) as! T
}
if T.self == Int64.self {
return try readInt(offset: &offset) as! T
}
if T.self == Bool.self {
return try readBoolean(offset: &offset) as! T
}
let valueSize = MemoryLayout<T>.size
guard let data = subdata(start: offset, length: valueSize) else { throw "Error reading primary value" }
let result = data.withUnsafeBytes {
$0.load(as: T.self)
}
offset += data.count
return result
}
func readData(offset: inout Int) throws -> Data {
let length = try readUint(offset: &offset)
guard let data = subdata(start: offset, length: Int(length)),
length == data.count else { throw "Error reading data" }
offset += Int(length)
return data
}
func readString(offset: inout Int) throws -> String {
let data = try readData(offset: &offset)
guard let result = String(data: data, encoding: .utf8)
else { throw "Error reading string" }
return result
}
private func readByte(offset: inout Int) throws -> UInt8 {
guard offset < count else { throw "Error reading byte" }
let b = self[offset]
offset += 1
return b
}
private func readUint(offset: inout Int) throws -> UInt64 {
var x: UInt64 = 0
var s: Int = 0
var i: Int = 0
while true {
let b = try readByte(offset: &offset)
if b < 0x80 {
if i > 9 || i == 9 && b > 1 {
throw "Invalid UInt"
}
return x | UInt64(b)<<s
}
x |= UInt64(b&0x7f) << s
s += 7
i += 1
}
}
private func readInt(offset: inout Int) throws -> Int64 {
let ux = try readUint(offset: &offset)
var x = Int64(ux >> 1)
if ux&1 != 0 {
x = ~x
}
return x
}
private func readBoolean(offset: inout Int) throws -> Bool {
return try readByte(offset: &offset) == 1
}
}
extension Data {
init(value: Any?) {
self.init()
if let value = value {
writeValue(value: value)
}
}
init(values: Any...) {
self.init()
values.forEach { writeValue(value: $0) }
}
mutating func writeValues(values: Any...) {
values.forEach { writeValue(value: $0) }
}
mutating func writeValue(value: Any) {
let oldLength = count
switch value {
case is NSNull: break
case let parsed as Data: writeData(parsed, sizePrefix: true)
case let parsed as Int64: writeInt(parsed)
case let parsed as UInt64: writeUint(parsed)
case let parsed as Int: writePrimary(parsed)
case let parsed as UInt8: writePrimary(parsed)
case let parsed as UInt16: writePrimary(parsed)
case let parsed as UInt32: writePrimary(parsed)
case let parsed as Float: writePrimary(parsed)
case let parsed as CGFloat: writePrimary(Double(parsed))
case let parsed as Double: writePrimary(parsed)
case let parsed as Bool: writeBoolean(parsed)
case let parsed as UIEdgeInsets:
writeValues(values: parsed.top, parsed.bottom, parsed.left, parsed.right)
case let parsed as CGRect:
writeValues(values: parsed.origin.x, parsed.origin.y, parsed.size.width, parsed.size.height)
case let parsed as CGPoint: writeValues(values: parsed.x, parsed.y)
case let parsed as CGSize: writeValues(values: parsed.width, parsed.height)
case let parsed as UIColor:
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
parsed.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
writeValues(values: red, green, blue, alpha)
case let parsed as String: writeString(parsed)
case let parsed as UIFont: writeNSCoding(parsed.fontDescriptor)
case let parsed as NSAttributedString: writeNSCoding(parsed)
default: break
}
if CFGetTypeID(value as CFTypeRef) == CGColor.typeID {
writeValue(value: UIColor(cgColor: value as! CGColor))
}
let length = count - oldLength
if length == 0 && !(value is NSNull) {
print("Nothing was written for \(String(describing: type(of: value))):\(value) ")
}
}
private mutating func writePrimary<T>(_ value: T) {
writeData(Swift.withUnsafeBytes(of: value) { Data($0) }, sizePrefix: false)
}
private mutating func writeString(_ string: String) {
let stringData = string.data(using: .utf8, allowLossyConversion: true) ?? Data()
writeData(stringData, sizePrefix: true)
}
private mutating func writeNSCoding(_ coding: NSCoding) {
do {
let valueData = try NSKeyedArchiver.archivedData(withRootObject: coding, requiringSecureCoding: true)
writeData(valueData, sizePrefix: true)
} catch {
print("Unexpected error: \(error).")
}
}
private mutating func writeData(_ data: Data, sizePrefix: Bool) {
if sizePrefix {
writeValue(value: UInt64(data.count))
}
append(data)
}
private mutating func writeUint(_ input: UInt64) {
var v = input
while v >= 0x80 {
append(UInt8(v.littleEndian & 0x7F) | 0x80) // v.littleEndian ?
v >>= 7
}
append(UInt8(v))
}
private mutating func writeInt(_ v: Int64) {
var uv = UInt64(v) << 1
if v < 0 {
uv = ~uv
}
writeUint(uv)
}
private mutating func writeBoolean(_ v: Bool) {
append(v ? 1 : 0)
}
}
extension Encodable {
func toJSONData() -> Data? { try? JSONEncoder().encode(self) }
}
extension String: Error {}

View file

@ -0,0 +1,17 @@
import Foundation
extension Date {
func format(_ format: String) -> String {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.dateFormat = format
return formatter.string(from: self)
}
static func parse(dateStr: String, format: String) -> Date? {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.dateFormat = format
return formatter.date(from: dateStr)
}
}

View file

@ -0,0 +1,10 @@
import Foundation
extension String {
func contains(regex: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: regex) else { return false }
let range = NSRange(location: 0, length: self.utf16.count)
return regex.firstMatch(in: self, options: [], range: range) != nil
}
}

View file

@ -0,0 +1,24 @@
import UIKit
extension UIImage {
func applyBlurWithRadius(_ blurRadius: CGFloat) -> UIImage? {
if (size.width < 1 || size.height < 1) {
return nil
}
guard let inputCGImage = self.cgImage else {
return nil
}
let inputImage = CIImage(cgImage: inputCGImage)
let filter = CIFilter(name: "CIGaussianBlur")
filter?.setValue(inputImage, forKey: kCIInputImageKey)
filter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
guard let outputImage = filter?.outputImage else {
return nil
}
let context = CIContext(options: nil)
guard let outputCGImage = context.createCGImage(outputImage, from: inputImage.extent) else {
return nil
}
return UIImage(cgImage: outputCGImage)
}
}

View file

@ -0,0 +1,26 @@
import UIKit
private var viewCounter = 0
private var shortIds = [String: String]()
extension UIView: Sanitizable {
public var identifier: String {
let longId = longIdentifier
if let existingId = shortIds[longId] {
return existingId
}
let shortId = "\(viewCounter)"
viewCounter += 1
shortIds[longId] = shortId
return shortId
}
public var longIdentifier: String {
return String(describing: type(of: self)) + "-" + Unmanaged.passUnretained(self).toOpaque().debugDescription
}
public var frameInWindow: CGRect? {
return self.window == nil ? nil : self.convert(self.bounds, to: self.window)
}
}

View file

@ -0,0 +1,288 @@
import UIKit
import SwiftUI
import Combine
import ObjectiveC
open class Analytics: NSObject {
public static let shared = Analytics()
public var enabled = false
public var observedInputs: [UITextField] = []
public var observedViews: [UIView] = []
private override init() {
super.init()
}
public func start() {
enabled = true
UIViewController.swizzleLifecycleMethods()
}
public func stop() {
observedViews.removeAll()
observedInputs.removeAll()
enabled = false
// Unswizzle (reverse the swizzling) if needed in the
}
@objc private func handleTap(gesture: UITapGestureRecognizer) {
let location = gesture.location(in: nil)
DebugUtils.log("Tap detected at: \(location)")
}
@objc public func addObservedInput(_ element: UITextField) {
observedInputs.append(element)
element.addTarget(self, action: #selector(textInputFinished), for: .editingDidEnd)
}
@objc public func addObservedView(view: UIView, screenName: String, viewName: String) {
view.orScreenName = screenName
view.orViewName = viewName
observedViews.append(view)
}
@objc public func sendClick(label: String, x: UInt64, y: UInt64) {
let message = ORIOSClickEvent(label: label, x: x, y: y)
if Analytics.shared.enabled {
MessageCollector.shared.sendMessage(message)
}
}
@objc public func sendSwipe(label: String, x: UInt64, y: UInt64, direction: String) {
let message = ORIOSSwipeEvent(label: label, x: x,y: y, direction: direction)
if Analytics.shared.enabled {
MessageCollector.shared.sendMessage(message)
}
}
@objc func textInputFinished(_ sender: UITextField) {
#if DEBUG
DebugUtils.log(">>>>>Text finish \(sender.text ?? "no_text") \(sender.placeholder ?? "no_placeholder")")
#endif
var sentText = sender.text
if sender.isSecureTextEntry {
sentText = "***"
}
MessageCollector.shared.sendMessage(ORIOSInputEvent(value: sentText ?? "", valueMasked: sender.isSecureTextEntry, label: sender.placeholder ?? ""))
}
}
extension UIViewController {
static func swizzleLifecycleMethods() {
DebugUtils.log(">>>>> ORTracker: swizzle UIViewController")
Self.swizzle(original: #selector(viewDidAppear(_:)), swizzled: #selector(swizzledViewDidAppear(_:)))
Self.swizzle(original: #selector(viewDidDisappear(_:)), swizzled: #selector(swizzledViewDidDisappear(_:)))
}
static private func swizzle(original: Selector, swizzled: Selector) {
if let originalMethod = class_getInstanceMethod(self, original),
let swizzledMethod = class_getInstanceMethod(self, swizzled) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
@objc private func swizzledViewDidAppear(_ animated: Bool) {
self.swizzledViewDidAppear(animated)
if let (screenName, viewName) = isViewOrSubviewObservedEnter() {
let message = ORIOSViewComponentEvent(screenName: screenName, viewName: viewName, visible: true)
if Analytics.shared.enabled {
MessageCollector.shared.sendMessage(message)
}
}
}
@objc private func swizzledViewDidDisappear(_ animated: Bool) {
self.swizzledViewDidDisappear(animated)
if let (screenName, viewName) = isViewOrSubviewObservedEnter() {
let message = ORIOSViewComponentEvent(screenName: screenName, viewName: viewName, visible: false)
if Analytics.shared.enabled {
MessageCollector.shared.sendMessage(message)
}
}
}
private func isViewOrSubviewObservedEnter() -> (screenName: String, viewName: String)? {
var viewsToCheck: [UIView] = [self.view]
while !viewsToCheck.isEmpty {
let view = viewsToCheck.removeFirst()
if let observed = Analytics.shared.observedViews.first(where: { $0 == view }) {
let screenName = observed.orScreenName ?? "Unknown ScreenName"
let viewName = observed.orViewName ?? "Unknown View"
return (screenName, viewName)
}
viewsToCheck.append(contentsOf: view.subviews)
}
return nil
}
}
public class TouchTrackingWindow: UIWindow {
var touchStart: CGPoint?
public override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
guard let touches = event.allTouches else { return }
for touch in touches {
switch touch.phase {
case .began:
touchStart = touch.location(in: self)
case .ended:
let location = touch.location(in: self)
let isSwipe = touchStart!.distance(to: location) > 10
var event: ORMessage
let description = getViewDescription(touch.view) ?? "UIView"
if isSwipe {
DebugUtils.log("Swipe from \(touchStart ?? CGPoint(x: 0, y: 0)) to \(location)")
event = ORIOSSwipeEvent(label: description, x: UInt64(location.x),y: UInt64(location.y), direction: detectSwipeDirection(from: touchStart!, to: location))
} else {
event = ORIOSClickEvent(label: description, x: UInt64(location.x), y: UInt64(location.y))
DebugUtils.log("Touch from \(touchStart ?? CGPoint(x: 0, y: 0)) to \(location)")
}
touchStart = nil
MessageCollector.shared.sendMessage(event)
default:
break
}
}
}
private func getViewDescription(_ view: UIView?) -> String? {
guard let view = view else {
return nil
}
if let textField = view as? UITextField {
return "UITextField '\(textField.placeholder ?? "No Placeholder")'"
} else if let label = view as? UILabel {
return "UILabel '\(label.text ?? "No Text")'"
} else if let button = view as? UIButton {
return "UIButton '\(button.currentTitle ?? "No Title")'"
} else if let textView = view as? UITextView {
return "UITextView '\(textView.text ?? "No Text")'"
} else {
return "\(type(of: view))"
}
}
private func detectSwipeDirection(from start: CGPoint, to end: CGPoint) -> String {
let deltaX = end.x - start.x
let deltaY = end.y - start.y
if abs(deltaX) > abs(deltaY) {
if deltaX > 0 {
return "right"
} else {
return "left"
}
} else if abs(deltaY) > abs(deltaX) {
if deltaY > 0 {
return "down"
} else {
return "up"
}
}
return "right"
}
}
extension CGPoint {
func distance(to point: CGPoint) -> CGFloat {
return hypot(point.x - x, point.y - y)
}
}
public struct ObservedInputModifier: ViewModifier {
@Binding var text: String
let label: String?
let masked: Bool?
public func body(content: Content) -> some View {
content
.onReceive(text.publisher.collect()) { value in
let stringValue = String(value)
textInputFinished(value: stringValue, label: label, masked: masked)
}
}
private func textInputFinished(value: String, label: String?, masked: Bool?) {
guard !value.isEmpty else { return }
var sentValue = value
if masked ?? false {
sentValue = "****"
}
MessageCollector.shared.sendDebouncedMessage(ORIOSInputEvent(value: sentValue, valueMasked: masked ?? false, label: label ?? ""))
}
}
public struct ViewLifecycleModifier: ViewModifier {
let screenName: String
let viewName: String
public func body(content: Content) -> some View {
content
.onAppear {
DebugUtils.log("<><><>view appear \(viewName)")
let message = ORIOSViewComponentEvent(screenName: screenName, viewName: viewName, visible: true)
if Analytics.shared.enabled {
MessageCollector.shared.sendMessage(message)
}
}
.onDisappear {
DebugUtils.log("<><><>disappear view \(viewName)")
let message = ORIOSViewComponentEvent(screenName: screenName, viewName: viewName, visible: false)
if Analytics.shared.enabled {
MessageCollector.shared.sendMessage(message)
}
}
}
}
public extension View {
func observeView(screenName: String, viewName: String) -> some View {
self.modifier(ViewLifecycleModifier(screenName: screenName, viewName: viewName))
}
func observeInput(text: Binding<String>, label: String?, masked: Bool?) -> some View {
self.modifier(ObservedInputModifier(text: text, label: label, masked: masked))
}
}
extension UIView {
private struct AssociatedKeys {
static var orScreenName: String = "OR: screenName"
static var orViewName: String = "OR: viewName"
}
var orScreenName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.orScreenName) as? String
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.orScreenName, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var orViewName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.orViewName) as? String
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.orViewName, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

View file

@ -0,0 +1,64 @@
import UIKit
public class Crashs: NSObject {
public static let shared = Crashs()
private static var fileUrl: URL? = nil
private var isActive = false
private override init() {
Crashs.fileUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("ASCrash.dat")
if let fileUrl = Crashs.fileUrl,
FileManager.default.fileExists(atPath: fileUrl.path),
let crashData = try? Data(contentsOf: fileUrl) {
NetworkManager.shared.sendLateMessage(content: crashData) { (success) in
guard success else { return }
if FileManager.default.fileExists(atPath: fileUrl.path) {
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
}
public func start() {
NSSetUncaughtExceptionHandler { (exception) in
print("<><> captured crash \(exception)")
let message = ORIOSCrash(name: exception.name.rawValue,
reason: exception.reason ?? "",
stacktrace: exception.callStackSymbols.joined(separator: "\n"))
let messageData = message.contentData()
if let fileUrl = Crashs.fileUrl {
try? messageData.write(to: fileUrl)
}
NetworkManager.shared.sendMessage(content: messageData) { (success) in
guard success else { return }
if let fileUrl = Crashs.fileUrl,
FileManager.default.fileExists(atPath: fileUrl.path) {
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
isActive = true
}
public func sendLateError(exception: NSException) {
let message = ORIOSCrash(name: exception.name.rawValue,
reason: exception.reason ?? "",
stacktrace: exception.callStackSymbols.joined(separator: "\n")
)
NetworkManager.shared.sendLateMessage(content: message.contentData()) { (success) in
guard success else { return }
if let fileUrl = Crashs.fileUrl,
FileManager.default.fileExists(atPath: fileUrl.path) {
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
public func stop() {
if isActive {
NSSetUncaughtExceptionHandler(nil)
isActive = false
}
}
}

View file

@ -0,0 +1,47 @@
import UIKit
class LogsListener: NSObject {
static let shared = LogsListener()
private let outputListener = Listener(fileHandle: FileHandle.standardOutput, severity: "info")
private let errorListener = Listener(fileHandle: FileHandle.standardError, severity: "error")
func start() {
outputListener.start()
errorListener.start()
}
class Listener: NSObject {
let inputPipe = Pipe()
let outputPipe = Pipe()
let fileHandle: FileHandle
let severity: String
init(fileHandle: FileHandle, severity: String) {
self.fileHandle = fileHandle
self.severity = severity
super.init()
inputPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
guard let strongSelf = self else { return }
let data = fileHandle.availableData
if let string = String(data: data, encoding: String.Encoding.utf8) {
let message = ORIOSLog(severity: severity, content: string)
MessageCollector.shared.sendMessage(message)
}
strongSelf.outputPipe.fileHandleForWriting.write(data)
}
}
func start() {
dup2(fileHandle.fileDescriptor, outputPipe.fileHandleForWriting.fileDescriptor)
dup2(inputPipe.fileHandleForWriting.fileDescriptor, fileHandle.fileDescriptor)
}
func stop() {
}
}
}

View file

@ -0,0 +1,150 @@
import UIKit
open class NetworkListener: NSObject {
private let startTime: UInt64
private var url: String = ""
private var method: String = ""
private var requestBody: String?
private var requestHeaders: [String: String]?
var ignoredKeys = ["password"]
var ignoredHeaders = ["Authentication", "Auth"]
public override init() {
startTime = UInt64(Date().timeIntervalSince1970 * 1000)
}
public convenience init(request: URLRequest) {
self.init()
start(request: request)
}
public convenience init(task: URLSessionTask) {
self.init()
start(task: task)
}
open func start(request: URLRequest) {
url = request.url?.absoluteString ?? ""
method = request.httpMethod ?? "GET"
requestHeaders = request.allHTTPHeaderFields
if let body = request.httpBody {
requestBody = String(data: body, encoding: .utf8)
} else {
requestBody = ""
DebugUtils.log("error getting request body (start request)")
}
}
open func start(task: URLSessionTask) {
if let request = task.currentRequest {
start(request: request)
} else {
DebugUtils.log("error getting request body (start task)")
}
}
open func finish(response: URLResponse?, data: Data?) {
let endTime = UInt64(Date().timeIntervalSince1970 * 1000)
let httpResponse = response as? HTTPURLResponse
var responseBody: String? = nil
if let data = data {
responseBody = String(data: data, encoding: .utf8)
} else {
DebugUtils.log("error getting request body (finish)")
}
let requestContent: [String: Any?] = [
"body": sanitizeBody(body: requestBody),
"headers": sanitizeHeaders(headers: requestHeaders)
]
var responseContent: [String: Any?]
if let httpResponse = httpResponse {
let headers = transformHeaders(httpResponse.allHeaderFields)
responseContent = [
"body": sanitizeBody(body: responseBody),
"headers": sanitizeHeaders(headers: headers)
]
} else {
responseContent = [
"body": "",
"headers": ""
]
}
let requestJSON = convertDictionaryToJSONString(dictionary: requestContent) ?? ""
let responseJSON = convertDictionaryToJSONString(dictionary: responseContent) ?? ""
let status = httpResponse?.statusCode ?? 0
let message = ORIOSNetworkCall(
type: "request",
method: method,
URL: url,
request: requestJSON,
response: responseJSON,
status: UInt64(status),
duration: endTime - startTime
)
MessageCollector.shared.sendMessage(message)
}
private func sanitizeHeaders(headers: [String: String]?) -> [String: String]? {
guard let headerContent = headers else { return nil }
var sanitizedHeaders = headerContent
for key in ignoredKeys {
if sanitizedHeaders.keys.contains(key) {
sanitizedHeaders[key] = "***"
}
}
return sanitizedHeaders
}
private func sanitizeBody(body: String?) -> String? {
guard let bodyContent = body else { return nil }
var sanitizedBody = bodyContent
for key in ignoredKeys {
if let range = sanitizedBody.range(of: "\"\(key)\":\"[^\"]*\"", options: .regularExpression) {
sanitizedBody.replaceSubrange(range, with: "\"\(key)\":\"***\"")
}
}
return sanitizedBody
}
}
func convertDictionaryToJSONString(dictionary: [String: Any?]) -> String? {
if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: []) {
return String(data: jsonData, encoding: .utf8)
}
return nil
}
func transformHeaders(_ headers: [AnyHashable: Any]) -> [String: String] {
var stringHeaders: [String: String] = [:]
for (key, value) in headers {
if let stringKey = key.base as? String, let stringValue = value as? String {
stringHeaders[stringKey] = stringValue
}
}
return stringHeaders
}
func isJSONString(string: String) -> Bool {
if let data = string.data(using: .utf8) {
do {
_ = try JSONSerialization.jsonObject(with: data, options: [])
return true
} catch {
DebugUtils.log("Error: \(error)")
return false
}
}
return false
}

View file

@ -0,0 +1,177 @@
import UIKit
open class PerformanceListener: NSObject {
public static let shared = PerformanceListener()
private var cpuTimer: Timer?
private var cpuIteration = 0
private var memTimer: Timer?
public var isActive = false
func start() {
#warning("Can interfere with client usage")
UIDevice.current.isBatteryMonitoringEnabled = true
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
let observe: (Notification.Name) -> Void = {
NotificationCenter.default.addObserver(self, selector: #selector(self.notified(_:)), name: $0, object: nil)
}
observe(.NSBundleResourceRequestLowDiskSpace)
observe(.NSProcessInfoPowerStateDidChange)
observe(ProcessInfo.thermalStateDidChangeNotification)
observe(UIApplication.didReceiveMemoryWarningNotification)
observe(UIDevice.batteryLevelDidChangeNotification)
observe(UIDevice.batteryStateDidChangeNotification)
observe(UIDevice.orientationDidChangeNotification)
getCpuMessage()
getMemoryMessage()
cpuTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { (_) in
self.getCpuMessage()
})
memTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: { (_) in
self.getMemoryMessage()
})
isActive = true
NotificationCenter.default.addObserver(self, selector: #selector(pause), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(resume), name: UIApplication.willEnterForegroundNotification, object: nil)
}
@objc func resume() {
#if DEBUG
DebugUtils.log("Resume")
#endif
getCpuMessage()
getMemoryMessage()
MessageCollector.shared.sendMessage(ORIOSPerformanceEvent(name: "background", value: UInt64(0)))
}
@objc func pause() {
#if DEBUG
DebugUtils.log("Background")
#endif
MessageCollector.shared.sendMessage(ORIOSPerformanceEvent(name: "background", value: UInt64(1)))
}
func getCpuMessage() {
if let cpu = self.cpuUsage() {
MessageCollector.shared.sendMessage(ORIOSPerformanceEvent(name: "mainThreadCPU", value: UInt64(cpu)))
}
}
func getMemoryMessage() {
if let mem = self.memoryUsage() {
MessageCollector.shared.sendMessage(ORIOSPerformanceEvent(name: "memoryUsage", value: UInt64(mem)))
}
}
func stop() {
if isActive {
UIDevice.current.isBatteryMonitoringEnabled = false
UIDevice.current.endGeneratingDeviceOrientationNotifications()
NotificationCenter.default.removeObserver(self, name: .NSBundleResourceRequestLowDiskSpace, object: nil)
NotificationCenter.default.removeObserver(self, name: .NSProcessInfoPowerStateDidChange, object: nil)
NotificationCenter.default.removeObserver(self, name: ProcessInfo.thermalStateDidChangeNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIDevice.batteryLevelDidChangeNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIDevice.batteryStateDidChangeNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
cpuTimer?.invalidate()
cpuTimer = nil
memTimer?.invalidate()
memTimer = nil
isActive = false
}
}
public func sendBattery() {
let message = ORIOSPerformanceEvent(name: "batteryLevel", value: 20)
MessageCollector.shared.sendMessage(message)
}
public func sendThermal() {
let message2 = ORIOSPerformanceEvent(name: "thermalState", value: 2)
MessageCollector.shared.sendMessage(message2)
}
@objc func notified(_ notification: Notification) {
var message: ORIOSPerformanceEvent? = nil
switch notification.name {
case .NSBundleResourceRequestLowDiskSpace:
message = ORIOSPerformanceEvent(name: "lowDiskSpace", value: 0)
case .NSProcessInfoPowerStateDidChange:
message = ORIOSPerformanceEvent(name: "isLowPowerModeEnabled", value: ProcessInfo.processInfo.isLowPowerModeEnabled ? 1 : 0)
case ProcessInfo.thermalStateDidChangeNotification:
message = ORIOSPerformanceEvent(name: "thermalState", value: UInt64(ProcessInfo.processInfo.thermalState.rawValue))
case UIApplication.didReceiveMemoryWarningNotification:
message = ORIOSPerformanceEvent(name: "memoryWarning", value: 0)
case UIDevice.batteryLevelDidChangeNotification:
message = ORIOSPerformanceEvent(name: "batteryLevel", value: UInt64(max(0.0, UIDevice.current.batteryLevel)*100))
case UIDevice.batteryStateDidChangeNotification:
message = ORIOSPerformanceEvent(name: "batteryState", value: UInt64(UIDevice.current.batteryState.rawValue))
case UIDevice.orientationDidChangeNotification:
message = ORIOSPerformanceEvent(name: "orientation", value: UInt64(UIDevice.current.orientation.rawValue))
default: break
}
if let message = message {
MessageCollector.shared.sendMessage(message)
}
}
func networkStateChange(_ state: UInt64) {
let message = ORIOSPerformanceEvent(name: "networkState", value: state)
MessageCollector.shared.sendMessage(message)
}
func memoryUsage() -> UInt64? {
var taskInfo = task_vm_info_data_t()
var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size) / 4
let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
}
}
guard result == KERN_SUCCESS else {
return nil
}
return UInt64(taskInfo.phys_footprint)
}
func cpuUsage() -> Double? {
var threadsListContainer: thread_act_array_t?
var threadsCount = mach_msg_type_number_t(0)
let threadsResult = withUnsafeMutablePointer(to: &threadsListContainer) {
return $0.withMemoryRebound(to: thread_act_array_t?.self, capacity: 1) {
task_threads(mach_task_self_, $0, &threadsCount)
}
}
defer {
vm_deallocate(mach_task_self_, vm_address_t(UInt(bitPattern: threadsListContainer)), vm_size_t(Int(threadsCount) * MemoryLayout<thread_t>.stride))
}
guard threadsCount > 0, threadsResult == KERN_SUCCESS, let threadsList = threadsListContainer else {
return nil
}
var threadInfo = thread_basic_info()
var threadInfoCount = mach_msg_type_number_t(THREAD_INFO_MAX)
let infoResult = withUnsafeMutablePointer(to: &threadInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
thread_info(threadsList[0], thread_flavor_t(THREAD_BASIC_INFO), $0, &threadInfoCount)
}
}
let threadBasicInfo = threadInfo as thread_basic_info
guard infoResult == KERN_SUCCESS, threadBasicInfo.flags & TH_FLAGS_IDLE == 0 else { return nil }
return Double(threadBasicInfo.cpu_usage) / Double(TH_USAGE_SCALE) * 100.0
}
}

View file

@ -0,0 +1,16 @@
import Foundation
class DebugUtils: NSObject {
static func error(_ str: String) {
// TODO: fix this one
// MessageCollector.shared.sendMessage(ASIOSInternalError(content: str))
log(str)
}
static func log(_ str: String) {
#if DEBUG
print(str)
#endif
}
}

View file

@ -0,0 +1,168 @@
import UIKit
struct BatchArch {
var name: String
var data: Data
}
class MessageCollector: NSObject {
public static let shared = MessageCollector()
private var imagesWaiting = [BatchArch]()
private var imagesSending = [BatchArch]()
private var messagesWaiting = [Data]()
private var nextMessageIndex = 0
private var sendingLastMessages = false
private let maxMessagesSize = 500_000
private let messagesQueue = OperationQueue()
private let lateMessagesFile: URL?
private var sendInderval: Timer?
override init() {
lateMessagesFile = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("/lateMessages.dat")
super.init()
}
func start() {
sendInderval = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [weak self] _ in
self?.flush()
})
NotificationCenter.default.addObserver(self, selector: #selector(terminate), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(terminate), name: UIApplication.willTerminateNotification, object: nil)
messagesQueue.maxConcurrentOperationCount = 1
if let fileUrl = lateMessagesFile,
FileManager.default.fileExists(atPath: fileUrl.path),
let lateData = try? Data(contentsOf: fileUrl) {
NetworkManager.shared.sendLateMessage(content: lateData) { (success) in
guard success else { return }
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
func stop() {
sendInderval?.invalidate()
NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.willTerminateNotification, object: nil)
self.terminate()
}
func sendImagesBatch(batch: Data, fileName: String) {
self.imagesWaiting.append(BatchArch(name: fileName, data: batch))
messagesQueue.addOperation {
self.flushImages()
}
}
@objc func terminate() {
guard !sendingLastMessages else { return }
messagesQueue.addOperation {
self.sendingLastMessages = true
self.flushMessages()
}
}
@objc func flush() {
messagesQueue.addOperation {
self.flushMessages()
self.flushImages()
}
}
private func flushImages() {
let images = imagesWaiting.first
guard !imagesWaiting.isEmpty, let images = images, let projectKey = ORTracker.shared.projectKey else { return }
imagesWaiting.remove(at: 0)
imagesSending.append(images)
DebugUtils.log("Sending images \(images.name) \(images.data.count)")
NetworkManager.shared.sendImages(projectKey: projectKey, images: images.data, name: images.name) { (success) in
self.imagesSending.removeAll { (waiting) -> Bool in
images.name == waiting.name
}
guard success else {
self.imagesWaiting.append(images)
return
}
}
}
func sendMessage(_ message: ORMessage) {
let data = message.contentData()
#if DEBUG
if !message.description.contains("IOSLog") && !message.description.contains("IOSNetworkCall") {
DebugUtils.log(message.description)
}
if let networkCallMessage = message as? ORIOSNetworkCall {
DebugUtils.log("-->> IOSNetworkCall(105): \(networkCallMessage.method) \(networkCallMessage.URL)")
}
#endif
self.sendRawMessage(data)
}
private var debounceTimer: Timer?
private var debouncedMessage: ORMessage?
func sendDebouncedMessage(_ message: ORMessage) {
debounceTimer?.invalidate()
debouncedMessage = message
debounceTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
if let debouncedMessage = self?.debouncedMessage {
self?.sendMessage(debouncedMessage)
self?.debouncedMessage = nil
}
}
}
func sendRawMessage(_ data: Data) {
messagesQueue.addOperation {
if data.count > self.maxMessagesSize {
DebugUtils.log("<><><>Single message size exceeded limit")
return
}
self.messagesWaiting.append(data)
var totalWaitingSize = 0
self.messagesWaiting.forEach { totalWaitingSize += $0.count }
if totalWaitingSize > Int(Double(self.maxMessagesSize) * 0.8) {
self.flushMessages()
}
}
}
private func flushMessages() {
var messages = [Data]()
var sentSize = 0
while let message = messagesWaiting.first, sentSize + message.count <= maxMessagesSize {
messages.append(message)
messagesWaiting.remove(at: 0)
sentSize += message.count
}
guard !messages.isEmpty else { return }
var content = Data()
let index = ORIOSBatchMeta(firstIndex: UInt64(nextMessageIndex))
content.append(index.contentData())
DebugUtils.log(index.description)
messages.forEach { (message) in
content.append(message)
}
if sendingLastMessages, let fileUrl = lateMessagesFile {
try? content.write(to: fileUrl)
}
nextMessageIndex += messages.count
DebugUtils.log("messages batch \(content)")
NetworkManager.shared.sendMessage(content: content) { (success) in
guard success else {
DebugUtils.log("<><>re-sending failed batch<><>")
self.messagesWaiting.insert(contentsOf: messages, at: 0)
return
}
if self.sendingLastMessages {
self.sendingLastMessages = false
if let fileUrl = self.lateMessagesFile, FileManager.default.fileExists(atPath: fileUrl.path) {
try? FileManager.default.removeItem(at: fileUrl)
}
}
}
}
}

View file

@ -0,0 +1,195 @@
import UIKit
import SWCompression
let START_URL = "/v1/mobile/start"
let INGEST_URL = "/v1/mobile/i"
let LATE_URL = "/v1/mobile/late"
let IMAGES_URL = "/v1/mobile/images"
class NetworkManager: NSObject {
static let shared = NetworkManager()
var baseUrl = "https://api.openreplay.com/ingest"
public var sessionId: String? = nil
private var token: String? = nil
public var writeToFile = false
#if DEBUG
private let localFilePath = "/Users/nikitamelnikov/Desktop/session.dat"
#endif
override init() {
#if DEBUG
if writeToFile, FileManager.default.fileExists(atPath: localFilePath) {
try? FileManager.default.removeItem(at: URL(fileURLWithPath: localFilePath))
}
#endif
}
private func createRequest(method: String, path: String) -> URLRequest {
let url = URL(string: baseUrl+path)!
var request = URLRequest(url: url)
request.httpMethod = method
return request
}
private func callAPI(request: URLRequest,
onSuccess: @escaping (Data) -> Void,
onError: @escaping (Error?) -> Void) {
guard !writeToFile else { return }
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
DebugUtils.log(">>>\(request.httpMethod ?? ""):\(request.url?.absoluteString ?? "")\n<<<\(String(data: data ?? Data(), encoding: .utf8) ?? "")")
DispatchQueue.main.async {
guard let data = data,
let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
DebugUtils.error(">>>>>> Error in call \(request.url?.absoluteString ?? "") : \(error?.localizedDescription ?? "N/A")")
if (response as? HTTPURLResponse)?.statusCode == 401 {
self.token = nil
ORTracker.shared.startSession(projectKey: ORTracker.shared.projectKey ?? "", options: ORTracker.shared.options)
}
onError(error)
return
}
onSuccess(data)
}
}
task.resume()
}
func createSession(params: [String: AnyHashable], completion: @escaping (ORSessionResponse?) -> Void) {
guard !writeToFile else {
self.token = "writeToFile"
return
}
var request = createRequest(method: "POST", path: START_URL)
guard let jsonData = try? JSONSerialization.data(withJSONObject: params, options: []) else {
completion(nil)
return
}
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
callAPI(request: request) { (data) in
do {
let session = try JSONDecoder().decode(ORSessionResponse.self, from: data)
self.token = session.token
self.sessionId = session.sessionID
ORUserDefaults.shared.lastToken = self.token
completion(session)
} catch {
DebugUtils.log("Can't unwrap session start resp: \(error)")
}
} onError: { _ in
completion(nil)
}
}
func sendMessage(content: Data, completion: @escaping (Bool) -> Void) {
guard !writeToFile else {
appendLocalFile(data: content)
return
}
var request = createRequest(method: "POST", path: INGEST_URL)
guard let token = token else {
completion(false)
return
}
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
var compressedContent = content
let oldSize = compressedContent.count
var newSize = oldSize
do {
let compressed = try GzipArchive.archive(data: content)
compressedContent = compressed
newSize = compressed.count
request.setValue("gzip", forHTTPHeaderField: "Content-Encoding")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
DebugUtils.log(">>>>Compress batch file \(oldSize)>\(newSize)")
} catch {
DebugUtils.log("Error with compression: \(error)")
}
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.httpBody = compressedContent
callAPI(request: request) { (data) in
completion(true)
} onError: { _ in
completion(false)
}
}
func sendLateMessage(content: Data, completion: @escaping (Bool) -> Void) {
DebugUtils.log(">>>sending late messages")
var request = createRequest(method: "POST", path: LATE_URL)
guard let token = ORUserDefaults.shared.lastToken else {
completion(false)
DebugUtils.log("! No last token found")
return
}
print(token)
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.httpBody = content
callAPI(request: request) { (data) in
completion(true)
DebugUtils.log("<<< late messages sent")
} onError: { _ in
completion(false)
}
}
func sendImages(projectKey: String, images: Data, name: String, completion: @escaping (Bool) -> Void) {
var request = createRequest(method: "POST", path: IMAGES_URL)
guard let token = token else {
completion(false)
return
}
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let boundary = "Boundary-\(NSUUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
let parameters = ["projectKey": projectKey]
for (key, value) in parameters {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"batch\"; filename=\"\(name)\"\r\n")
body.appendString("Content-Type: gzip\r\n\r\n")
body.append(images)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
DebugUtils.log(">>>>>> sending \(body.count) bytes")
request.httpBody = body
callAPI(request: request) { (data) in
completion(true)
} onError: { _ in
completion(false)
}
}
private func appendLocalFile(data: Data) {
#if DEBUG
DebugUtils.log("appendInFile \(data.count) bytes")
let fileURL = URL(fileURLWithPath: localFilePath)
if let fileHandle = try? FileHandle(forWritingTo: fileURL) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(data)
} else {
try? data.write(to: fileURL, options: .atomic)
}
#endif
}
}

View file

@ -0,0 +1,33 @@
import UIKit
class ORUserDefaults: NSObject {
public static let shared = ORUserDefaults()
private let userDefaults: UserDefaults?
override init() {
userDefaults = UserDefaults(suiteName: "io.orenreplay.openreplaytr-defaults")
}
var userUUID: String {
get {
if let savedUUID = userDefaults?.string(forKey: "userUUID") {
return savedUUID
}
let newUUID = UUID().uuidString
self.userUUID = newUUID
return newUUID
}
set {
userDefaults?.set(newValue, forKey: "userUUID")
}
}
var lastToken: String? {
get {
return userDefaults?.string(forKey: "lastToken")
}
set {
userDefaults?.set(newValue, forKey: "lastToken")
}
}
}

View file

@ -0,0 +1,22 @@
import UIKit
class Swizzling: NSObject {
static func swizzle(cls: AnyClass, original: Selector, swizzled: Selector) {
if let originalMethod = class_getInstanceMethod(cls, original),
let swizzledMethod = class_getInstanceMethod(cls, swizzled) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
static func swizzleIfPresent(cls: AnyClass, original: Selector, swizzled: Selector) {
if let originalMethod = class_getInstanceMethod(cls, original),
let swizzledMethod = class_getInstanceMethod(cls, swizzled) {
let didAddMethod = class_addMethod(self, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
}

View file

@ -0,0 +1,42 @@
import UIKit
struct GenericMessage {
let typeRaw: UInt64
let type: ORMessageType?
let timestamp: UInt64
let body: Data
init?(data: Data, offset: inout Int) {
do {
typeRaw = try data.readPrimary(offset: &offset)
type = ORMessageType(rawValue: typeRaw)
timestamp = try data.readPrimary(offset: &offset)
body = try data.readData(offset: &offset)
} catch {
return nil
}
}
}
class ORMessage: NSObject {
let messageRaw: UInt64
let message: ORMessageType?
let timestamp: UInt64
init(messageType: ORMessageType) {
self.messageRaw = messageType.rawValue
self.message = messageType
self.timestamp = UInt64(Date().timeIntervalSince1970 * 1000)
}
init?(genericMessage: GenericMessage) {
self.messageRaw = genericMessage.typeRaw
self.message = genericMessage.type
self.timestamp = genericMessage.timestamp
}
func contentData() -> Data {
fatalError("This method should be overridden")
}
}

View file

@ -0,0 +1,27 @@
import Foundation
@objc public enum RecordingQuality: Int {
case Low
case Standard
case High
}
open class OROptions: NSObject {
let crashes: Bool
let analytics: Bool
let performances: Bool
let logs: Bool
let screen: Bool
let wifiOnly: Bool
public static let defaults = OROptions(crashes: true, analytics: true, performances: true, logs: true, screen: true, wifiOnly: true)
@objc public init(crashes: Bool, analytics: Bool, performances: Bool, logs: Bool, screen: Bool, wifiOnly: Bool) {
self.crashes = crashes
self.analytics = analytics
self.performances = performances
self.logs = logs
self.screen = screen
self.wifiOnly = wifiOnly
}
}

View file

@ -0,0 +1,9 @@
import UIKit
struct ORRecord: Codable {
let img: String
let originX: CGFloat
let originY: CGFloat
let timestamp: UInt64
}

View file

@ -0,0 +1,84 @@
import UIKit
import DeviceKit
class ORSessionRequest: NSObject {
private static var params = [String: AnyHashable]()
static func create( completion: @escaping (ORSessionResponse?) -> Void) {
guard let projectKey = ORTracker.shared.projectKey else { return print("Openreplay: no project key added") }
#warning("Can interfere with client usage")
UIDevice.current.isBatteryMonitoringEnabled = true
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
let performances: [String: UInt64] = [
"physicalMemory": UInt64(ProcessInfo.processInfo.physicalMemory),
"processorCount": UInt64(ProcessInfo.processInfo.processorCount),
"activeProcessorCount": UInt64(ProcessInfo.processInfo.activeProcessorCount),
"systemUptime": UInt64(ProcessInfo.processInfo.systemUptime),
"isLowPowerModeEnabled": UInt64(ProcessInfo.processInfo.isLowPowerModeEnabled ? 1 : 0),
"thermalState": UInt64(ProcessInfo.processInfo.thermalState.rawValue),
"batteryLevel": UInt64(max(0.0, UIDevice.current.batteryLevel)*100),
"batteryState": UInt64(UIDevice.current.batteryState.rawValue),
"orientation": UInt64(UIDevice.current.orientation.rawValue),
]
let device = Device.current
var deviceModel = ""
var deviceSafeName = ""
if device.isSimulator {
deviceSafeName = "iPhone 14 Pro"
deviceModel = "iPhone14,8"
} else {
deviceSafeName = device.safeDescription
deviceModel = Device.identifier
}
DebugUtils.log(">>>> device \(device) type \(device.safeDescription) mem \(UInt64(ProcessInfo.processInfo.physicalMemory / 1024))")
params = [
"projectKey": projectKey,
"trackerVersion": Bundle(for: ORTracker.shared.classForCoder).object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "N/A",
"revID": Bundle(for: ORTracker.shared.classForCoder).object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "N/A",
"userUUID": ORUserDefaults.shared.userUUID,
"userOSVersion": UIDevice.current.systemVersion,
"userDevice": deviceModel,
"userDeviceType": deviceSafeName,
"timestamp": UInt64(Date().timeIntervalSince1970 * 1000),
"performances": performances,
"deviceMemory": UInt64(ProcessInfo.processInfo.physicalMemory / 1024),
"timezone": getTimezone(),
]
callAPI(completion: completion)
}
private static func callAPI(completion: @escaping (ORSessionResponse?) -> Void) {
guard !params.isEmpty else { return }
NetworkManager.shared.createSession(params: params) { (sessionResponse) in
guard let sessionResponse = sessionResponse else {
DispatchQueue.global().asyncAfter(deadline: .now() + 5) {
callAPI(completion: completion)
}
return
}
DebugUtils.log(">>>> Starting session : \(sessionResponse.sessionID)")
return completion(sessionResponse)
}
}
}
struct ORSessionResponse: Decodable {
let userUUID: String
let token: String
let imagesHashList: [String]?
let sessionID: String
let fps: Int
let quality: String
}
func getTimezone() -> String {
let offset = TimeZone.current.secondsFromGMT()
let sign = offset >= 0 ? "+" : "-"
let hours = abs(offset) / 3600
let minutes = (abs(offset) % 3600) / 60
return String(format: "UTC%@%02d:%02d", sign, hours, minutes)
}

View file

@ -0,0 +1,499 @@
// Auto-generated, do not edit
import UIKit
enum ORMessageType: UInt64 {
case iOSMetadata = 92
case iOSEvent = 93
case iOSUserID = 94
case iOSUserAnonymousID = 95
case iOSScreenChanges = 96
case iOSCrash = 97
case iOSViewComponentEvent = 98
case iOSClickEvent = 100
case iOSInputEvent = 101
case iOSPerformanceEvent = 102
case iOSLog = 103
case iOSInternalError = 104
case iOSNetworkCall = 105
case iOSSwipeEvent = 106
case iOSBatchMeta = 107
}
class ORIOSMetadata: ORMessage {
let key: String
let value: String
init(key: String, value: String) {
self.key = key
self.value = value
super.init(messageType: .iOSMetadata)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.key = try genericMessage.body.readString(offset: &offset)
self.value = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(92), timestamp, Data(values: key, value))
}
override var description: String {
return "-->> IOSMetadata(92): timestamp:\(timestamp) key:\(key) value:\(value)";
}
}
class ORIOSEvent: ORMessage {
let name: String
let payload: String
init(name: String, payload: String) {
self.name = name
self.payload = payload
super.init(messageType: .iOSEvent)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.name = try genericMessage.body.readString(offset: &offset)
self.payload = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(93), timestamp, Data(values: name, payload))
}
override var description: String {
return "-->> IOSEvent(93): timestamp:\(timestamp) name:\(name) payload:\(payload)";
}
}
class ORIOSUserID: ORMessage {
let iD: String
init(iD: String) {
self.iD = iD
super.init(messageType: .iOSUserID)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.iD = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(94), timestamp, Data(values: iD))
}
override var description: String {
return "-->> IOSUserID(94): timestamp:\(timestamp) iD:\(iD)";
}
}
class ORIOSUserAnonymousID: ORMessage {
let iD: String
init(iD: String) {
self.iD = iD
super.init(messageType: .iOSUserAnonymousID)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.iD = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(95), timestamp, Data(values: iD))
}
override var description: String {
return "-->> IOSUserAnonymousID(95): timestamp:\(timestamp) iD:\(iD)";
}
}
class ORIOSScreenChanges: ORMessage {
let x: UInt64
let y: UInt64
let width: UInt64
let height: UInt64
init(x: UInt64, y: UInt64, width: UInt64, height: UInt64) {
self.x = x
self.y = y
self.width = width
self.height = height
super.init(messageType: .iOSScreenChanges)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.x = try genericMessage.body.readPrimary(offset: &offset)
self.y = try genericMessage.body.readPrimary(offset: &offset)
self.width = try genericMessage.body.readPrimary(offset: &offset)
self.height = try genericMessage.body.readPrimary(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(96), timestamp, Data(values: x, y, width, height))
}
override var description: String {
return "-->> IOSScreenChanges(96): timestamp:\(timestamp) x:\(x) y:\(y) width:\(width) height:\(height)";
}
}
class ORIOSCrash: ORMessage {
let name: String
let reason: String
let stacktrace: String
init(name: String, reason: String, stacktrace: String) {
self.name = name
self.reason = reason
self.stacktrace = stacktrace
super.init(messageType: .iOSCrash)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.name = try genericMessage.body.readString(offset: &offset)
self.reason = try genericMessage.body.readString(offset: &offset)
self.stacktrace = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(97), timestamp, Data(values: name, reason, stacktrace))
}
override var description: String {
return "-->> IOSCrash(97): timestamp:\(timestamp) name:\(name) reason:\(reason) stacktrace:\(stacktrace)";
}
}
class ORIOSViewComponentEvent: ORMessage {
let screenName: String
let viewName: String
let visible: Bool
init(screenName: String, viewName: String, visible: Bool) {
self.screenName = screenName
self.viewName = viewName
self.visible = visible
super.init(messageType: .iOSViewComponentEvent)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.screenName = try genericMessage.body.readString(offset: &offset)
self.viewName = try genericMessage.body.readString(offset: &offset)
self.visible = try genericMessage.body.readPrimary(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(98), timestamp, Data(values: screenName, viewName, visible))
}
override var description: String {
return "-->> IOSViewComponentEvent(98): timestamp:\(timestamp) screenName:\(screenName) viewName:\(viewName) visible:\(visible)";
}
}
class ORIOSClickEvent: ORMessage {
let label: String
let x: UInt64
let y: UInt64
init(label: String, x: UInt64, y: UInt64) {
self.label = label
self.x = x
self.y = y
super.init(messageType: .iOSClickEvent)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.label = try genericMessage.body.readString(offset: &offset)
self.x = try genericMessage.body.readPrimary(offset: &offset)
self.y = try genericMessage.body.readPrimary(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(100), timestamp, Data(values: label, x, y))
}
override var description: String {
return "-->> IOSClickEvent(100): timestamp:\(timestamp) label:\(label) x:\(x) y:\(y)";
}
}
class ORIOSInputEvent: ORMessage {
let value: String
let valueMasked: Bool
let label: String
init(value: String, valueMasked: Bool, label: String) {
self.value = value
self.valueMasked = valueMasked
self.label = label
super.init(messageType: .iOSInputEvent)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.value = try genericMessage.body.readString(offset: &offset)
self.valueMasked = try genericMessage.body.readPrimary(offset: &offset)
self.label = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(101), timestamp, Data(values: value, valueMasked, label))
}
override var description: String {
return "-->> IOSInputEvent(101): timestamp:\(timestamp) value:\(value) valueMasked:\(valueMasked) label:\(label)";
}
}
class ORIOSPerformanceEvent: ORMessage {
let name: String
let value: UInt64
init(name: String, value: UInt64) {
self.name = name
self.value = value
super.init(messageType: .iOSPerformanceEvent)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.name = try genericMessage.body.readString(offset: &offset)
self.value = try genericMessage.body.readPrimary(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(102), timestamp, Data(values: name, value))
}
override var description: String {
return "-->> IOSPerformanceEvent(102): timestamp:\(timestamp) name:\(name) value:\(value)";
}
}
class ORIOSLog: ORMessage {
let severity: String
let content: String
init(severity: String, content: String) {
self.severity = severity
self.content = content
super.init(messageType: .iOSLog)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.severity = try genericMessage.body.readString(offset: &offset)
self.content = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(103), timestamp, Data(values: severity, content))
}
override var description: String {
return "-->> IOSLog(103): timestamp:\(timestamp) severity:\(severity) content:\(content)";
}
}
class ORIOSInternalError: ORMessage {
let content: String
init(content: String) {
self.content = content
super.init(messageType: .iOSInternalError)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.content = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(104), timestamp, Data(values: content))
}
override var description: String {
return "-->> IOSInternalError(104): timestamp:\(timestamp) content:\(content)";
}
}
class ORIOSNetworkCall: ORMessage {
let type: String
let method: String
let URL: String
let request: String
let response: String
let status: UInt64
let duration: UInt64
init(type: String, method: String, URL: String, request: String, response: String, status: UInt64, duration: UInt64) {
self.type = type
self.method = method
self.URL = URL
self.request = request
self.response = response
self.status = status
self.duration = duration
super.init(messageType: .iOSNetworkCall)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.type = try genericMessage.body.readString(offset: &offset)
self.method = try genericMessage.body.readString(offset: &offset)
self.URL = try genericMessage.body.readString(offset: &offset)
self.request = try genericMessage.body.readString(offset: &offset)
self.response = try genericMessage.body.readString(offset: &offset)
self.status = try genericMessage.body.readPrimary(offset: &offset)
self.duration = try genericMessage.body.readPrimary(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(105), timestamp, Data(values: type, method, URL, request, response, status, duration))
}
override var description: String {
return "-->> IOSNetworkCall(105): timestamp:\(timestamp) type:\(type) method:\(method) URL:\(URL) request:\(request) response:\(response) status:\(status) duration:\(duration)";
}
}
class ORIOSSwipeEvent: ORMessage {
let label: String
let x: UInt64
let y: UInt64
let direction: String
init(label: String, x: UInt64, y: UInt64, direction: String) {
self.label = label
self.x = x
self.y = y
self.direction = direction
super.init(messageType: .iOSSwipeEvent)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.label = try genericMessage.body.readString(offset: &offset)
self.x = try genericMessage.body.readPrimary(offset: &offset)
self.y = try genericMessage.body.readPrimary(offset: &offset)
self.direction = try genericMessage.body.readString(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(106), timestamp, Data(values: label, x, y, direction))
}
override var description: String {
return "-->> IOSSwipeEvent(106): timestamp:\(timestamp) label:\(label) x:\(x) y:\(y) direction:\(direction)";
}
}
class ORIOSBatchMeta: ORMessage {
let firstIndex: UInt64
init(firstIndex: UInt64) {
self.firstIndex = firstIndex
super.init(messageType: .iOSBatchMeta)
}
override init?(genericMessage: GenericMessage) {
do {
var offset = 0
self.firstIndex = try genericMessage.body.readPrimary(offset: &offset)
super.init(genericMessage: genericMessage)
} catch {
return nil
}
}
override func contentData() -> Data {
return Data(values: UInt64(107), timestamp, Data(values: firstIndex))
}
override var description: String {
return "-->> IOSBatchMeta(107): timestamp:\(timestamp) firstIndex:\(firstIndex)";
}
}

View file

@ -0,0 +1,37 @@
// Auto-generated, do not edit
import UIKit
enum ORMessageType: UInt64 {
<%= $messages.map { |msg| " case #{msg.name.first_lower} = #{msg.id}" }.join "\n" %>
}
<% $messages.each do |msg| %>
class OR<%= msg.name.to_s.camel_case %>: ORMessage {
<%= msg.attributes[2..-1].map { |attr| " let #{attr.property}: #{attr.type_swift}" }.join "\n" %>
init(<%= msg.attributes[2..-1].map { |attr| "#{attr.property}: #{attr.type_swift}" }.join ", " %>) {
<%= msg.attributes[2..-1].map { |attr| " self.#{attr.property} = #{attr.property}" }.join "\n" %>
super.init(messageType: .<%= "#{msg.name.first_lower}" %>)
}
override init?(genericMessage: GenericMessage) {
<% if msg.attributes.length > 2 %> do {
var offset = 0
<%= msg.attributes[2..-1].map { |attr| " self.#{attr.property} = try genericMessage.body.read#{attr.type_swift_read}(offset: &offset)" }.join "\n" %>
super.init(genericMessage: genericMessage)
} catch {
return nil
}
<% else %>
super.init(genericMessage: genericMessage)
<% end %>}
override func contentData() -> Data {
return Data(values: UInt64(<%= "#{msg.id}"%>), timestamp<% if msg.attributes.length > 2 %>, Data(values: <%= msg.attributes[2..-1].map { |attr| attr.property }.join ", "%>)<% end %>)
}
override var description: String {
return "-->> <%= msg.name.to_s.camel_case %>(<%= "#{msg.id}"%>): timestamp:\(timestamp) <%= msg.attributes[2..-1].map { |attr| "#{attr.property}:\\(#{attr.property})" }.join " "%>";
}
}
<% end %>

View file

@ -0,0 +1,129 @@
message 92, 'IOSMetadata' do
uint 'Timestamp'
uint 'Length'
string 'Key'
string 'Value'
end
message 93, 'IOSEvent' do
uint 'Timestamp'
uint 'Length'
string 'Name'
string 'Payload'
end
message 94, 'IOSUserID' do
uint 'Timestamp'
uint 'Length'
string 'ID'
end
message 95, 'IOSUserAnonymousID' do
uint 'Timestamp'
uint 'Length'
string 'ID'
end
message 96, 'IOSScreenChanges' do
uint 'Timestamp'
uint 'Length'
uint 'X'
uint 'Y'
uint 'Width'
uint 'Height'
end
message 97, 'IOSCrash' do
uint 'Timestamp'
uint 'Length'
string 'Name'
string 'Reason'
string 'Stacktrace'
end
message 98, 'IOSViewComponentEvent' do
uint 'Timestamp'
uint 'Length'
string 'ScreenName'
string 'ViewName'
boolean 'Visible'
end
message 100, 'IOSClickEvent' do
uint 'Timestamp'
uint 'Length'
string 'Label'
uint 'X'
uint 'Y'
end
message 101, 'IOSInputEvent' do
uint 'Timestamp'
uint 'Length'
string 'Value'
boolean 'ValueMasked'
string 'Label'
end
=begin
Name/Value may be :
"physicalMemory": Total memory in bytes
"processorCount": Total processors in device
"activeProcessorCount": Number of currently used processors
"systemUptime": Elapsed time (in seconds) since last boot
"isLowPowerModeEnabled": Possible values (1 or 0)
"thermalState": Possible values (0:nominal 1:fair 2:serious 3:critical)
"batteryLevel": Possible values (0 .. 100)
"batteryState": Possible values (0:unknown 1:unplugged 2:charging 3:full)
"orientation": Possible values (0unknown 1:portrait 2:portraitUpsideDown 3:landscapeLeft 4:landscapeRight 5:faceUp 6:faceDown)
"mainThreadCPU": Possible values (0 .. 100)
"memoryUsage": Used memory in bytes
"fps": Frames per second
=end
message 102, 'IOSPerformanceEvent' do
uint 'Timestamp'
uint 'Length'
string 'Name'
uint 'Value'
end
message 103, 'IOSLog' do
uint 'Timestamp'
uint 'Length'
string 'Severity' # Possible values ("info", "error")
string 'Content'
end
message 104, 'IOSInternalError' do
uint 'Timestamp'
uint 'Length'
string 'Content'
end
message 105, 'IOSNetworkCall' do
uint 'Timestamp'
uint 'Length'
string 'Type'
string 'Method'
string 'URL'
string 'Request'
string 'Response'
uint 'Status'
uint 'Duration'
end
message 106, 'IOSSwipeEvent' do
uint 'Timestamp'
uint 'Length'
string 'Label'
uint 'X'
uint 'Y'
string 'Direction'
end
message 107, 'IOSBatchMeta' do
uint 'Timestamp'
uint 'Length'
uint 'FirstIndex'
end

View file

@ -0,0 +1,114 @@
require 'erb'
class String
def camel_case
return self if self !~ /_/ && self =~ /[A-Z]+.*/
split('_').map{|e| e.capitalize}.join.upperize
end
def camel_case_lower
self.split('_').inject([]) do |buffer, e|
word = if e.downcase == "url"
"URL"
else
buffer.empty? ? e : e.capitalize
end
buffer.push(word)
end.join.upperize
end
def upperize
self.sub('Id', 'ID').sub('Url', 'URL').sub('url', 'URL')
end
def first_lower
return "URL" if self == "URL"
"" if self == ""
self[0].downcase + self[1..-1]
end
def underscore
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end
class Attribute
attr_reader :name, :type
def initialize(name:, type:)
@name = name
@type = type
end
def property
@name.first_lower
end
def type_swift
case @type
when :string
"String"
when :data
"Data"
when :uint
"UInt64"
when :boolean
"Bool"
else
"Primary"
end
end
def type_swift_read
case @type
when :string
"String"
when :data
"Data"
else
"Primary"
end
end
end
class Message
attr_reader :id, :name, :js, :replayer, :attributes
def initialize(name:, id:, js: true, replayer: true, &block)
@id = id
@name = name
@js = js
@replayer = replayer
@attributes = []
instance_eval &block
end
%i(uint string data boolean).each do |type|
define_method type do |name, opts = {}|
opts.merge!(
name: name,
type: type,
)
@attributes << Attribute.new(opts)
end
end
end
$ids = []
$messages = []
def message(id, name, opts = {}, &block)
raise "id duplicated #{name}" if $ids.include? id
raise "id is too big #{name}" if id > 120
$ids << id
opts[:id] = id
opts[:name] = name
msg = Message.new(opts, &block)
$messages << msg
end
require './messages.rb'
e = ERB.new(File.read('./messages.erb'))
File.write('ORMessages.swift', e.result)

View file

@ -0,0 +1,162 @@
import UIKit
import Network
public enum CheckState {
case unchecked
case canStart
case cantStart
}
open class ORTracker: NSObject {
@objc public static let shared = ORTracker()
public let userDefaults = UserDefaults(suiteName: "io.asayer.AsayerSDK-defaults")
public var projectKey: String?
public var trackerState = CheckState.unchecked
private var networkCheckTimer: Timer?
public var serverURL: String {
get { NetworkManager.shared.baseUrl }
set { NetworkManager.shared.baseUrl = newValue }
}
public var options: OROptions = OROptions.defaults
@objc open func start(projectKey: String, options: OROptions) {
self.options = options
let monitor = NWPathMonitor()
let q = DispatchQueue.global(qos: .background)
self.projectKey = projectKey
monitor.start(queue: q)
monitor.pathUpdateHandler = { path in
if path.usesInterfaceType(.wifi) {
if PerformanceListener.shared.isActive {
PerformanceListener.shared.networkStateChange(1)
}
self.trackerState = CheckState.canStart
} else if path.usesInterfaceType(.cellular) {
if PerformanceListener.shared.isActive {
PerformanceListener.shared.networkStateChange(0)
}
if options.wifiOnly {
self.trackerState = CheckState.cantStart
print("Connected to Cellular and options.wifiOnly is true. ORTracker will not start.")
} else {
self.trackerState = CheckState.canStart
}
} else {
self.trackerState = CheckState.cantStart
print("Not connected to either WiFi or Cellular. ORTracker will not start.")
}
}
networkCheckTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (_) in
if self.trackerState == CheckState.canStart {
self.startSession(projectKey: projectKey, options: options)
self.networkCheckTimer?.invalidate()
}
if self.trackerState == CheckState.cantStart {
self.networkCheckTimer?.invalidate()
}
})
}
@objc open func startSession(projectKey: String, options: OROptions) {
self.projectKey = projectKey
ORSessionRequest.create() { sessionResponse in
guard let sessionResponse = sessionResponse else { return print("Openreplay: no response from /start request") }
let captureSettings = getCaptureSettings(fps: sessionResponse.fps, quality: sessionResponse.quality)
ScreenshotManager.shared.setSettings(settings: captureSettings)
MessageCollector.shared.start()
if options.logs {
LogsListener.shared.start()
}
if options.crashes {
Crashs.shared.start()
}
if options.performances {
PerformanceListener.shared.start()
}
if options.screen {
ScreenshotManager.shared.start()
}
if options.analytics {
Analytics.shared.start()
}
}
}
@objc open func stop() {
MessageCollector.shared.stop()
ScreenshotManager.shared.stop()
Crashs.shared.stop()
PerformanceListener.shared.stop()
Analytics.shared.stop()
}
@objc open func addIgnoredView(_ view: UIView) {
ScreenshotManager.shared.addSanitizedElement(view)
}
@objc open func setMetadata(key: String, value: String) {
let message = ORIOSMetadata(key: key, value: value)
MessageCollector.shared.sendMessage(message)
}
@objc open func event(name: String, object: NSObject?) {
event(name: name, payload: object as? Encodable)
}
open func event(name: String, payload: Encodable?) {
var json = ""
if let payload = payload,
let data = payload.toJSONData(),
let jsonStr = String(data: data, encoding: .utf8) {
json = jsonStr
}
let message = ORIOSEvent(name: name, payload: json)
MessageCollector.shared.sendMessage(message)
}
open func eventStr(name: String, payload: String?) {
let message = ORIOSEvent(name: name, payload: payload ?? "")
MessageCollector.shared.sendMessage(message)
}
@objc open func setUserID(_ userID: String) {
let message = ORIOSUserID(iD: userID)
MessageCollector.shared.sendMessage(message)
}
@objc open func userAnonymousID(_ userID: String) {
let message = ORIOSUserAnonymousID(iD: userID)
MessageCollector.shared.sendMessage(message)
}
}
func getCaptureSettings(fps: Int, quality: String) -> (captureRate: Double, imgCompression: Double) {
let limitedFPS = min(max(fps, 1), 99)
let captureRate = 1.0 / Double(limitedFPS)
var imgCompression: Double
switch quality.lowercased() {
case "low":
imgCompression = 0.4
case "standard":
imgCompression = 0.5
case "high":
imgCompression = 0.6
default:
imgCompression = 0.5 // default to standard if quality string is not recognized
}
return (captureRate: captureRate, imgCompression: imgCompression)
}

View file

@ -0,0 +1,286 @@
import UIKit
import Foundation
import SwiftUI
import SWCompression
// MARK: - screenshot manager
open class ScreenshotManager {
public static let shared = ScreenshotManager()
private let messagesQueue = OperationQueue()
private var timer: Timer?
private var sendTimer: Timer?
private var sanitizedElements: [Sanitizable] = []
private var observedInputs: [UITextField] = []
private var screenshots: [Data] = []
private var lastIndex = 0
// MARK: capture settings
// should we blur out sensitive views, or place a solid box on top
private var isBlurMode = true
private var blurRadius = 2.5
// this affects how big the image will be compared to real phone screan.
// we also can use default UIScreen.main.scale which is around 3.0 (dense pixel screen)
private var screenScale = 1.25
private var settings: (captureRate: Double, imgCompression: Double) = (captureRate: 0.33, imgCompression: 0.5)
private init() { }
func start() {
startTakingScreenshots(every: settings.captureRate)
}
func setSettings(settings: (captureRate: Double, imgCompression: Double)) {
self.settings = settings
}
func stop() {
timer?.invalidate()
timer = nil
lastIndex = 0
screenshots.removeAll()
}
func startTakingScreenshots(every interval: TimeInterval) {
takeScreenshot()
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
self?.takeScreenshot()
}
}
public func addSanitizedElement(_ element: Sanitizable) {
#if DEBUG
DebugUtils.log("called add")
#endif
sanitizedElements.append(element)
}
public func removeSanitizedElement(_ element: Sanitizable) {
#if DEBUG
DebugUtils.log("called remove")
#endif
sanitizedElements.removeAll { $0 as AnyObject === element as AnyObject }
}
// MARK: - UI Capturing
func takeScreenshot() {
let window = UIApplication.shared.windows.first { $0.isKeyWindow }
let size = window?.frame.size ?? CGSize.zero
UIGraphicsBeginImageContextWithOptions(size, false, screenScale)
guard let context = UIGraphicsGetCurrentContext() else { return }
// Rendering current window in custom context
// 2nd option looks to be more precise
// window?.layer.render(in: context)
#warning("Can slow down the app depending on complexity of the UI tree")
window?.drawHierarchy(in: window?.bounds ?? CGRect.zero, afterScreenUpdates: false)
// MARK: sanitize
// Sanitizing sensitive elements
if isBlurMode {
let stripeWidth: CGFloat = 5.0
let stripeSpacing: CGFloat = 15.0
let stripeColor: UIColor = .gray.withAlphaComponent(0.7)
for element in sanitizedElements {
if let frame = element.frameInWindow {
let totalWidth = frame.size.width
let totalHeight = frame.size.height
let convertedFrame = CGRect(
x: frame.origin.x,
y: frame.origin.y,
width: frame.size.width,
height: frame.size.height
)
let cropFrame = CGRect(
x: frame.origin.x * screenScale,
y: frame.origin.y * screenScale,
width: frame.size.width * screenScale,
height: frame.size.height * screenScale
)
if let regionImage = UIGraphicsGetImageFromCurrentImageContext()?.cgImage?.cropping(to: cropFrame) {
let imageToBlur = UIImage(cgImage: regionImage, scale: screenScale, orientation: .up)
let blurredImage = imageToBlur.applyBlurWithRadius(blurRadius)
blurredImage?.draw(in: convertedFrame)
context.saveGState()
UIRectClip(convertedFrame)
// Draw diagonal lines within the clipped region
for x in stride(from: -totalHeight, to: totalWidth, by: stripeSpacing + stripeWidth) {
context.move(to: CGPoint(x: x + convertedFrame.minX, y: convertedFrame.minY))
context.addLine(to: CGPoint(x: x + totalHeight + convertedFrame.minX, y: totalHeight + convertedFrame.minY))
}
context.setLineWidth(stripeWidth)
stripeColor.setStroke()
context.strokePath()
context.restoreGState()
#if DEBUG
context.setStrokeColor(UIColor.black.cgColor)
context.setLineWidth(1)
context.stroke(convertedFrame)
#endif
}
} else {
removeSanitizedElement(element)
}
}
} else {
context.setFillColor(UIColor.blue.cgColor)
for element in sanitizedElements {
if let frame = element.frameInWindow {
context.fill(frame)
}
}
}
// Get the resulting image
if let image = UIGraphicsGetImageFromCurrentImageContext() {
if let compressedData = image.jpegData(compressionQuality: self.settings.imgCompression) {
screenshots.append(compressedData)
if screenshots.count >= 10 {
self.sendScreenshots()
}
}
}
UIGraphicsEndImageContext()
}
// Not using this because no idea how to sync it with the replay fps rn
//func onError() {
// takeScreenshot()
//}
// MARK: - sending screenshots
func sendScreenshots() {
guard let sessionId = NetworkManager.shared.sessionId else {
return
}
let localFilePath = "/Users/nikitamelnikov/Desktop/session/"
let desktopURL = URL(fileURLWithPath: localFilePath)
var archiveName = "\(sessionId)-\(String(format: "%06d", self.lastIndex)).tar.gz"
let archiveURL = desktopURL.appendingPathComponent(archiveName)
// Ensure the directory exists
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: localFilePath) {
try? fileManager.createDirectory(at: desktopURL, withIntermediateDirectories: true, attributes: nil)
}
var combinedData = Data()
let images = screenshots
for (index, imageData) in screenshots.enumerated() {
combinedData.append(imageData)
#if DEBUG
let filename = "\(lastIndex)_\(index).jpeg"
let fileURL = desktopURL.appendingPathComponent(filename)
do {
try imageData.write(to: fileURL)
} catch {
DebugUtils.log("Unexpected error: \(error).")
}
#endif
}
#if DEBUG
DebugUtils.log("saved image files in \(localFilePath)")
#endif
messagesQueue.addOperation {
var entries: [TarEntry] = []
for imageData in images {
let filename = "\(String(format: "%06d", self.lastIndex)).jpeg"
var tarEntry = TarContainer.Entry(info: .init(name: filename, type: .regular), data: imageData)
tarEntry.info.permissions = Permissions(rawValue: 420)
tarEntry.info.creationTime = Date()
tarEntry.info.modificationTime = Date()
entries.append(tarEntry)
self.lastIndex+=1
}
do {
let gzData = try GzipArchive.archive(data: TarContainer.create(from: entries))
#if DEBUG
try gzData.write(to: archiveURL)
DebugUtils.log("Archive saved to \(archiveURL.path)")
MessageCollector.shared.sendImagesBatch(batch: gzData, fileName: archiveName)
#else
MessageCollector.shared.sendImagesBatch(batch: gzData, fileName: archiveName)
#endif
} catch {
DebugUtils.log("Error writing tar.gz data: \(error)")
}
}
screenshots.removeAll()
}
}
// MARK: making extensions for UI
struct SensitiveViewWrapperRepresentable: UIViewRepresentable {
@Binding var viewWrapper: SensitiveViewWrapper?
func makeUIView(context: Context) -> SensitiveViewWrapper {
let wrapper = SensitiveViewWrapper()
viewWrapper = wrapper
return wrapper
}
func updateUIView(_ uiView: SensitiveViewWrapper, context: Context) { }
}
struct SensitiveModifier: ViewModifier {
@State private var viewWrapper: SensitiveViewWrapper?
func body(content: Content) -> some View {
content
.background(SensitiveViewWrapperRepresentable(viewWrapper: $viewWrapper))
}
}
public extension View {
func sensitive() -> some View {
self.modifier(SensitiveModifier())
}
}
class SensitiveViewWrapper: UIView {
override func didMoveToSuperview() {
super.didMoveToSuperview()
if self.superview != nil {
ScreenshotManager.shared.addSanitizedElement(self)
} else {
ScreenshotManager.shared.removeSanitizedElement(self)
}
}
}
class SensitiveTextField: UITextField {
override func didMoveToWindow() {
super.didMoveToWindow()
if self.window != nil {
ScreenshotManager.shared.addSanitizedElement(self)
} else {
ScreenshotManager.shared.removeSanitizedElement(self)
}
}
}
// Protocol to make a UIView sanitizable
public protocol Sanitizable {
var frameInWindow: CGRect? { get }
}
func getCaptureSettings(for quality: RecordingQuality) -> (captureRate: Double, imgCompression: Double) {
switch quality {
case .Low:
return (captureRate: 1, imgCompression: 0.4)
case .Standard:
return (captureRate: 0.33, imgCompression: 0.5)
case .High:
return (captureRate: 0.20, imgCompression: 0.55)
}
}

View file

@ -0,0 +1,11 @@
import XCTest
@testable import ORTracker
final class ORTrackerTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
// XCTAssertEqual(ORTracker().text, "Hello, World!")
}
}

View file

@ -0,0 +1,15 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

71
tracker/tracker-reactnative/.gitignore vendored Normal file
View file

@ -0,0 +1,71 @@
# OSX
#
.DS_Store
# XDE
.expo/
.env
example/.env
# VSCode
.vscode/
jsconfig.json
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IJ
#
.classpath
.cxx
.gradle
.idea
.project
.settings
local.properties
android.iml
# Cocoapods
#
example/ios/Pods
Pods
# Ruby
example/vendor/
# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore
# Expo
.expo/
# Turborepo
.turbo/
# generated by bob
lib/

View file

@ -0,0 +1 @@
v18

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,3 @@
# Override Yarn command so we can automatically setup the repo on running `yarn`
yarn-path "scripts/bootstrap.js"

View file

@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View file

@ -0,0 +1,114 @@
# Contributing
Contributions are always welcome, no matter how large or small!
We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
## Development workflow
To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
```sh
yarn
```
> While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development.
While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.
To start the packager:
```sh
yarn example start
```
To run the example app on Android:
```sh
yarn example android
```
To run the example app on iOS:
```sh
yarn example ios
```
Make sure your code passes TypeScript and ESLint. Run the following to verify:
```sh
yarn typecheck
yarn lint
```
To fix formatting errors, run the following:
```sh
yarn lint --fix
```
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
To edit the Objective-C or Swift files, open `example/ios/RntrackerExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > openreplay-reactnative`.
To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `openreplay-reactnative` under `Android`.
### Commit message convention
We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
- `feat`: new features, e.g. add new method to the module.
- `refactor`: code refactor, e.g. migrate from class components to hooks.
- `docs`: changes into documentation, e.g. add usage example for the module..
- `test`: adding or updating tests, e.g. add integration tests using detox.
- `chore`: tooling changes, e.g. change CI config.
Our pre-commit hooks verify that your commit message matches this format when committing.
### Linting and tests
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
Our pre-commit hooks verify that the linter and tests pass when committing.
### Publishing to npm
We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
To publish new versions, run the following:
```sh
yarn release
```
### Scripts
The `package.json` file contains various scripts for common tasks:
- `yarn bootstrap`: setup project by installing all dependencies and pods.
- `yarn typecheck`: type-check files with TypeScript.
- `yarn lint`: lint files with ESLint.
- `yarn test`: run unit tests with Jest.
- `yarn example start`: start the Metro server for the example app.
- `yarn example android`: run the example app on Android.
- `yarn example ios`: run the example app on iOS.
### Sending a pull request
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
When you're sending a pull request:
- Prefer small pull requests focused on one change.
- Verify that linters and tests are passing.
- Review the documentation to make sure it looks good.
- Follow the pull request template when opening a pull request.
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.

View file

@ -0,0 +1,44 @@
Elastic License 2.0 (ELv2)
**Acceptance**
By using the software, you agree to all of the terms and conditions below.
**Copyright License**
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below
**Limitations**
You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.
**Patents**
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
**Notices**
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
**No Other Rights**
These terms do not imply any licenses other than those expressly granted in these terms.
**Termination**
If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
**No Liability**
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
**Definitions**
The *licensor* is the entity offering these terms, and the *software* is the software the licensor makes available under these terms, including any portion of it.
*you* refers to the individual or entity agreeing to these terms.
*your company* is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. *control* means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
*your licenses* are all the licenses granted to you for the software under these terms.
*use* means anything you do with the software requiring one of your licenses.
*trademark* means trademarks, service marks, and similar rights.

View file

@ -0,0 +1,32 @@
# @openreplay/react-native
Only iOS devices are supported right now. Regular View and empty callbacks are used for android devices,
so its safe to use with crossplatform apps.
## Installation
```sh
npm install @openreplay/react-native
```
## Usage
```js
import { RntrackerView } from "@openreplay/react-native";
// ...
<RntrackerView color="tomato" />
```
## Contributing
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
## License
MIT
---
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)

View file

@ -0,0 +1,108 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["Rntracker_kotlinVersion"]
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}
def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Rntracker_" + name]
}
def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Rntracker_" + name]).toInteger()
}
def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()
// Namespace support was added in 7.3.0
if (major == 7 && minor >= 3) {
return true
}
return major >= 8
}
android {
if (supportsNamespace()) {
namespace "com.rntracker"
sourceSets {
main {
manifest.srcFile "src/main/AndroidManifestNew.xml"
}
}
}
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable "GradleCompatible"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
mavenCentral()
google()
}
def kotlin_version = getExtOrDefault("kotlinVersion")
dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "RntrackerView"
codegenJavaPackageName = "com.rntracker"
}
}

View file

@ -0,0 +1,5 @@
Rntracker_kotlinVersion=1.7.0
Rntracker_minSdkVersion=21
Rntracker_targetSdkVersion=31
Rntracker_compileSdkVersion=31
Rntracker_ndkversion=21.4.7075529

View file

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rntracker">
</manifest>

View file

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,17 @@
package com.rntracker
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class RntrackerPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return emptyList()
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(RntrackerViewManager())
}
}

View file

@ -0,0 +1,20 @@
package com.rntracker
import android.graphics.Color
import android.view.View
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp
class RntrackerViewManager : SimpleViewManager<View>() {
override fun getName() = "RntrackerView"
override fun createViewInstance(reactContext: ThemedReactContext): View {
return View(reactContext)
}
@ReactProp(name = "color")
fun setColor(view: View, color: String) {
view.setBackgroundColor(Color.parseColor(color))
}
}

View file

@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};

View file

@ -0,0 +1,2 @@
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1

View file

@ -0,0 +1 @@
.env

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,6 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
gem 'cocoapods', '~> 1.12'

View file

@ -0,0 +1,79 @@
This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
# Getting Started
>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
## Step 1: Start the Metro Server
First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native.
To start Metro, run the following command from the _root_ of your React Native project:
```bash
# using npm
npm start
# OR using Yarn
yarn start
```
## Step 2: Start your Application
Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app:
### For Android
```bash
# using npm
npm run android
# OR using Yarn
yarn android
```
### For iOS
```bash
# using npm
npm run ios
# OR using Yarn
yarn ios
```
If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively.
## Step 3: Modifying your App
Now that you have successfully run the app, let's modify it.
1. Open `App.tsx` in your text editor of choice and edit some lines.
2. For **Android**: Press the <kbd>R</kbd> key twice or select **"Reload"** from the **Developer Menu** (<kbd>Ctrl</kbd> + <kbd>M</kbd> (on Window and Linux) or <kbd>Cmd ⌘</kbd> + <kbd>M</kbd> (on macOS)) to see your changes!
For **iOS**: Hit <kbd>Cmd ⌘</kbd> + <kbd>R</kbd> in your iOS Simulator to reload the app and see your changes!
## Congratulations! :tada:
You've successfully run and modified your React Native App. :partying_face:
### Now what?
- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started).
# Troubleshooting
If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
# Learn More
To learn more about React Native, take a look at the following resources:
- [React Native Website](https://reactnative.dev) - learn more about React Native.
- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.

View file

@ -0,0 +1,123 @@
apply plugin: "com.android.application"
apply plugin: "com.facebook.react"
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
namespace "com.rntrackerexample"
defaultConfig {
applicationId "com.rntrackerexample"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View file

@ -0,0 +1,10 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
</application>
</manifest>

View file

@ -0,0 +1,75 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.rntrackerexample;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
/**
* Class responsible of loading Flipper inside your React Native application. This is the debug
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View file

@ -0,0 +1,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,32 @@
package com.rntrackerexample;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "RntrackerExample";
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
* (aka React 18) with two boolean flags.
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new DefaultReactActivityDelegate(
this,
getMainComponentName(),
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
DefaultNewArchitectureEntryPoint.getFabricEnabled());
}
}

View file

@ -0,0 +1,62 @@
package com.rntrackerexample;
import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
}

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
android:insetTop="@dimen/abc_edit_text_inset_top_material"
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
<selector>
<!--
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
-->
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
</selector>
</inset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">RntrackerExample</string>
</resources>

View file

@ -0,0 +1,9 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
</style>
</resources>

View file

@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.rntrackerexample;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
/**
* Class responsible of loading Flipper inside your React Native application. This is the release
* flavor of it so it's empty as we don't want to load Flipper.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
// Do nothing as we don't want to initialize Flipper on Release.
}
}

View file

@ -0,0 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
}
}

View file

@ -0,0 +1,44 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.182.0
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true

View file

@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -0,0 +1,244 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,4 @@
rootProject.name = 'RntrackerExample'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')

View file

@ -0,0 +1,4 @@
{
"name": "RntrackerExample",
"displayName": "RntrackerExample"
}

View file

@ -0,0 +1,17 @@
const path = require('path');
const pak = require('../package.json');
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
[
'module-resolver',
{
extensions: ['.tsx', '.ts', '.js', '.json'],
alias: {
[pak.name]: path.join(__dirname, '..', pak.source),
},
},
],
],
};

View file

@ -0,0 +1,5 @@
import { AppRegistry } from 'react-native';
import App from './src/App';
import { name as appName } from './app.json';
AppRegistry.registerComponent(appName, () => App);

View file

@ -0,0 +1,6 @@
//
// File.swift
// RntrackerExample
//
import Foundation

View file

@ -0,0 +1,66 @@
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, '13.0'
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
# dependencies: {
# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'RntrackerExample' do
use_frameworks! :linkage => :dynamic
config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
:hermes_enabled => flags[:hermes_enabled],
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
#:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
Dir['../../']
pod 'ORTracker', :path => '../../../tracker-ios'
target 'RntrackerExampleTests' do
inherit! :complete
# Pods for testing
end
post_install do |installer|
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end

View file

@ -0,0 +1,671 @@
PODS:
- BitByteData (2.0.3)
- boost (1.76.0)
- DeviceKit (5.1.0)
- DoubleConversion (1.1.6)
- FBLazyVector (0.72.4)
- FBReactNativeSpec (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- RCTRequired (= 0.72.4)
- RCTTypeSafety (= 0.72.4)
- React-Core (= 0.72.4)
- React-jsi (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- fmt (6.2.1)
- glog (0.3.5)
- hermes-engine (0.72.4):
- hermes-engine/Pre-built (= 0.72.4)
- hermes-engine/Pre-built (0.72.4)
- libevent (2.1.12)
- openreplay-reactnative (0.1.0):
- ORTracker
- RCT-Folly (= 2021.07.22.00)
- React-Core
- ORTracker (0.1.0):
- DeviceKit
- SWCompression
- RCT-Folly (2021.07.22.00):
- boost
- DoubleConversion
- fmt (~> 6.2.1)
- glog
- RCT-Folly/Default (= 2021.07.22.00)
- RCT-Folly/Default (2021.07.22.00):
- boost
- DoubleConversion
- fmt (~> 6.2.1)
- glog
- RCT-Folly/Futures (2021.07.22.00):
- boost
- DoubleConversion
- fmt (~> 6.2.1)
- glog
- libevent
- RCTRequired (0.72.4)
- RCTTypeSafety (0.72.4):
- FBLazyVector (= 0.72.4)
- RCTRequired (= 0.72.4)
- React-Core (= 0.72.4)
- React (0.72.4):
- React-Core (= 0.72.4)
- React-Core/DevSupport (= 0.72.4)
- React-Core/RCTWebSocket (= 0.72.4)
- React-RCTActionSheet (= 0.72.4)
- React-RCTAnimation (= 0.72.4)
- React-RCTBlob (= 0.72.4)
- React-RCTImage (= 0.72.4)
- React-RCTLinking (= 0.72.4)
- React-RCTNetwork (= 0.72.4)
- React-RCTSettings (= 0.72.4)
- React-RCTText (= 0.72.4)
- React-RCTVibration (= 0.72.4)
- React-callinvoker (0.72.4)
- React-Codegen (0.72.4):
- DoubleConversion
- FBReactNativeSpec
- glog
- hermes-engine
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core
- React-jsi
- React-jsiexecutor
- React-NativeModulesApple
- React-rncore
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- React-Core (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default (= 0.72.4)
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/CoreModulesHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/Default (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/DevSupport (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default (= 0.72.4)
- React-Core/RCTWebSocket (= 0.72.4)
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-jsinspector (= 0.72.4)
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTActionSheetHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTAnimationHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTBlobHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTImageHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTLinkingHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTNetworkHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTSettingsHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTTextHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTVibrationHeaders (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-Core/RCTWebSocket (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Core/Default (= 0.72.4)
- React-cxxreact
- React-hermes
- React-jsi
- React-jsiexecutor
- React-perflogger
- React-runtimeexecutor
- React-utils
- SocketRocket (= 0.6.1)
- Yoga
- React-CoreModules (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- RCTTypeSafety (= 0.72.4)
- React-Codegen (= 0.72.4)
- React-Core/CoreModulesHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- React-RCTBlob
- React-RCTImage (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- SocketRocket (= 0.6.1)
- React-cxxreact (0.72.4):
- boost (= 1.76.0)
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-callinvoker (= 0.72.4)
- React-debug (= 0.72.4)
- React-jsi (= 0.72.4)
- React-jsinspector (= 0.72.4)
- React-logger (= 0.72.4)
- React-perflogger (= 0.72.4)
- React-runtimeexecutor (= 0.72.4)
- React-debug (0.72.4)
- React-hermes (0.72.4):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- RCT-Folly/Futures (= 2021.07.22.00)
- React-cxxreact (= 0.72.4)
- React-jsi
- React-jsiexecutor (= 0.72.4)
- React-jsinspector (= 0.72.4)
- React-perflogger (= 0.72.4)
- React-jsi (0.72.4):
- boost (= 1.76.0)
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-jsiexecutor (0.72.4):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-cxxreact (= 0.72.4)
- React-jsi (= 0.72.4)
- React-perflogger (= 0.72.4)
- React-jsinspector (0.72.4)
- React-logger (0.72.4):
- glog
- React-NativeModulesApple (0.72.4):
- hermes-engine
- React-callinvoker
- React-Core
- React-cxxreact
- React-jsi
- React-runtimeexecutor
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- React-perflogger (0.72.4)
- React-RCTActionSheet (0.72.4):
- React-Core/RCTActionSheetHeaders (= 0.72.4)
- React-RCTAnimation (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- RCTTypeSafety (= 0.72.4)
- React-Codegen (= 0.72.4)
- React-Core/RCTAnimationHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-RCTAppDelegate (0.72.4):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core
- React-CoreModules
- React-hermes
- React-NativeModulesApple
- React-RCTImage
- React-RCTNetwork
- React-runtimescheduler
- ReactCommon/turbomodule/core
- React-RCTBlob (0.72.4):
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-Codegen (= 0.72.4)
- React-Core/RCTBlobHeaders (= 0.72.4)
- React-Core/RCTWebSocket (= 0.72.4)
- React-jsi (= 0.72.4)
- React-RCTNetwork (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-RCTImage (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- RCTTypeSafety (= 0.72.4)
- React-Codegen (= 0.72.4)
- React-Core/RCTImageHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- React-RCTNetwork (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-RCTLinking (0.72.4):
- React-Codegen (= 0.72.4)
- React-Core/RCTLinkingHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-RCTNetwork (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- RCTTypeSafety (= 0.72.4)
- React-Codegen (= 0.72.4)
- React-Core/RCTNetworkHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-RCTSettings (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- RCTTypeSafety (= 0.72.4)
- React-Codegen (= 0.72.4)
- React-Core/RCTSettingsHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-RCTText (0.72.4):
- React-Core/RCTTextHeaders (= 0.72.4)
- React-RCTVibration (0.72.4):
- RCT-Folly (= 2021.07.22.00)
- React-Codegen (= 0.72.4)
- React-Core/RCTVibrationHeaders (= 0.72.4)
- React-jsi (= 0.72.4)
- ReactCommon/turbomodule/core (= 0.72.4)
- React-rncore (0.72.4)
- React-runtimeexecutor (0.72.4):
- React-jsi (= 0.72.4)
- React-runtimescheduler (0.72.4):
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-callinvoker
- React-debug
- React-jsi
- React-runtimeexecutor
- React-utils (0.72.4):
- glog
- RCT-Folly (= 2021.07.22.00)
- React-debug
- ReactCommon/turbomodule/bridging (0.72.4):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-callinvoker (= 0.72.4)
- React-cxxreact (= 0.72.4)
- React-jsi (= 0.72.4)
- React-logger (= 0.72.4)
- React-perflogger (= 0.72.4)
- ReactCommon/turbomodule/core (0.72.4):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- React-callinvoker (= 0.72.4)
- React-cxxreact (= 0.72.4)
- React-jsi (= 0.72.4)
- React-logger (= 0.72.4)
- React-perflogger (= 0.72.4)
- SocketRocket (0.6.1)
- SWCompression (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/BZip2 (= 4.8.5)
- SWCompression/Deflate (= 4.8.5)
- SWCompression/GZip (= 4.8.5)
- SWCompression/LZ4 (= 4.8.5)
- SWCompression/LZMA (= 4.8.5)
- SWCompression/LZMA2 (= 4.8.5)
- SWCompression/SevenZip (= 4.8.5)
- SWCompression/TAR (= 4.8.5)
- SWCompression/XZ (= 4.8.5)
- SWCompression/ZIP (= 4.8.5)
- SWCompression/Zlib (= 4.8.5)
- SWCompression/BZip2 (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/Deflate (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/GZip (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/Deflate
- SWCompression/LZ4 (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/LZMA (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/LZMA2 (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/LZMA
- SWCompression/SevenZip (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/LZMA2
- SWCompression/TAR (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/XZ (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/LZMA2
- SWCompression/ZIP (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/Deflate
- SWCompression/Zlib (4.8.5):
- BitByteData (~> 2.0)
- SWCompression/Deflate
- Yoga (1.14.0)
DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- libevent (~> 2.1.12)
- openreplay-reactnative (from `../..`)
- ORTracker (from `../../../tracker-ios`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Codegen (from `build/generated/ios`)
- React-Core (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
- React-debug (from `../node_modules/react-native/ReactCommon/react/debug`)
- React-hermes (from `../node_modules/react-native/ReactCommon/hermes`)
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
- React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`)
- React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
- React-RCTImage (from `../node_modules/react-native/Libraries/Image`)
- React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`)
- React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`)
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-rncore (from `../node_modules/react-native/ReactCommon`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
trunk:
- BitByteData
- DeviceKit
- fmt
- libevent
- SocketRocket
- SWCompression
EXTERNAL SOURCES:
boost:
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
DoubleConversion:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
FBLazyVector:
:path: "../node_modules/react-native/Libraries/FBLazyVector"
FBReactNativeSpec:
:path: "../node_modules/react-native/React/FBReactNativeSpec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2023-08-07-RNv0.72.4-813b2def12bc9df02654b3e3653ae4a68d0572e0
openreplay-reactnative:
:path: "../.."
ORTracker:
:path: "../../../tracker-ios"
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTRequired:
:path: "../node_modules/react-native/Libraries/RCTRequired"
RCTTypeSafety:
:path: "../node_modules/react-native/Libraries/TypeSafety"
React:
:path: "../node_modules/react-native/"
React-callinvoker:
:path: "../node_modules/react-native/ReactCommon/callinvoker"
React-Codegen:
:path: build/generated/ios
React-Core:
:path: "../node_modules/react-native/"
React-CoreModules:
:path: "../node_modules/react-native/React/CoreModules"
React-cxxreact:
:path: "../node_modules/react-native/ReactCommon/cxxreact"
React-debug:
:path: "../node_modules/react-native/ReactCommon/react/debug"
React-hermes:
:path: "../node_modules/react-native/ReactCommon/hermes"
React-jsi:
:path: "../node_modules/react-native/ReactCommon/jsi"
React-jsiexecutor:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
React-NativeModulesApple:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
React-perflogger:
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-RCTActionSheet:
:path: "../node_modules/react-native/Libraries/ActionSheetIOS"
React-RCTAnimation:
:path: "../node_modules/react-native/Libraries/NativeAnimation"
React-RCTAppDelegate:
:path: "../node_modules/react-native/Libraries/AppDelegate"
React-RCTBlob:
:path: "../node_modules/react-native/Libraries/Blob"
React-RCTImage:
:path: "../node_modules/react-native/Libraries/Image"
React-RCTLinking:
:path: "../node_modules/react-native/Libraries/LinkingIOS"
React-RCTNetwork:
:path: "../node_modules/react-native/Libraries/Network"
React-RCTSettings:
:path: "../node_modules/react-native/Libraries/Settings"
React-RCTText:
:path: "../node_modules/react-native/Libraries/Text"
React-RCTVibration:
:path: "../node_modules/react-native/Libraries/Vibration"
React-rncore:
:path: "../node_modules/react-native/ReactCommon"
React-runtimeexecutor:
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
React-runtimescheduler:
:path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler"
React-utils:
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
BitByteData: 95b9b252c3b8d936df5e87750301f7a60542069d
boost: 57d2868c099736d80fcd648bf211b4431e51a558
DeviceKit: bcce5c26d9502e77e3a9fa5d1282877db388d922
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 5d4a3b7f411219a45a6d952f77d2c0a6c9989da5
FBReactNativeSpec: 3fc2d478e1c4b08276f9dd9128f80ec6d5d85c1f
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 81191603c4eaa01f5e4ae5737a9efcf64756c7b2
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
openreplay-reactnative: 195b9e120d1aed7bdde7d4728a2f23b37b8d5266
ORTracker: 5b993fad6248d9e5605c28f4af6454022f43934f
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: c0569ecc035894e4a68baecb30fe6a7ea6e399f9
RCTTypeSafety: e90354072c21236e0bcf1699011e39acd25fea2f
React: a1be3c6dc0a6e949ccd3e659781aa47bbae1868f
React-callinvoker: 1020b33f6cb1a1824f9ca2a86609fbce2a73c6ed
React-Codegen: d3cabb5a8b9de7b0fc5928c0cf478e73a178d728
React-Core: 34da8c952844e21f5a69bf46438fb6bf063900d0
React-CoreModules: 621243df8863055ff30f3bb2ff71573ffa7b16c8
React-cxxreact: 4ad1cc861e32fb533dad6ff7a4ea25680fa1c994
React-debug: cc0911f6e8ef0b198724c1d8d2c114eee7a059ec
React-hermes: 37377d0a56aa0cf55c65248271866ce3268cde3f
React-jsi: 6de8b0ccc6b765b58e4eee9ee38049dbeaf5c221
React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594
React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f
React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77
React-NativeModulesApple: 38a88481feb279e37deb740414abc732c6768c93
React-perflogger: 496a1a3dc6737f964107cb3ddae7f9e265ddda58
React-RCTActionSheet: 02904b932b50e680f4e26e7a686b33ebf7ef3c00
React-RCTAnimation: e44fe5053708c5be762943087c78dc5a6a3c645c
React-RCTAppDelegate: 75566e142d0c325f603790943a38f0e297bcbab3
React-RCTBlob: 5577c3542fafbd9241f505bda541f6762996b0eb
React-RCTImage: f48859ae6a1e5c2c8659f860efdfcb8b0cb60cde
React-RCTLinking: 9b145f380c6b5910e3219bef73e1d7024381ad54
React-RCTNetwork: 179f50a80c01fa7d6a515552a24ad2e294f341a1
React-RCTSettings: cdc9f538f0f8e72e6f3de7c971fb4fb2f49caff9
React-RCTText: 9b9f5589d9b649d7246c3f336e116496df28cfe6
React-RCTVibration: 35eec7201c8ffdaccdd908a5ec874b7055f975f3
React-rncore: 46133d523155fc84572338bd6a7c461c1d873efc
React-runtimeexecutor: d465ba0c47ef3ed8281143f59605cacc2244d5c7
React-runtimescheduler: b5bc431f8bbc35d6112b541086bba72061bf7c1d
React-utils: 23a3a714dec3c5727c07a017c37993c3a492b441
ReactCommon: d615a572332dd5b915b10a6fe7006b1bc67ee849
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
SWCompression: 15e38b06c37077399a1b60bfecc1c2cd71f0ee99
Yoga: 3efc43e0d48686ce2e8c60f99d4e6bd349aff981
PODFILE CHECKSUM: f12bcbf8dfa25c96bb19ba57e6413b29c875f421
COCOAPODS: 1.12.1

View file

@ -0,0 +1,3 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View file

@ -0,0 +1,686 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* RntrackerExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* RntrackerExampleTests.m */; };
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
60ADCB349F6D4931641744DE /* Pods_RntrackerExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EB70A6C522028E464541BDD /* Pods_RntrackerExample.framework */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
97DF8FD3FE651CA8010130D7 /* Pods_RntrackerExample_RntrackerExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D22699E0FFA1C22F7AE3448 /* Pods_RntrackerExample_RntrackerExampleTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = RntrackerExample;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
00E356EE1AD99517003FC87E /* RntrackerExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RntrackerExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* RntrackerExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RntrackerExampleTests.m; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* RntrackerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RntrackerExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RntrackerExample/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = RntrackerExample/AppDelegate.mm; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RntrackerExample/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RntrackerExample/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RntrackerExample/main.m; sourceTree = "<group>"; };
2D22699E0FFA1C22F7AE3448 /* Pods_RntrackerExample_RntrackerExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RntrackerExample_RntrackerExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5EB70A6C522028E464541BDD /* Pods_RntrackerExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RntrackerExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RntrackerExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
9473576378E546844F1249CB /* Pods-RntrackerExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RntrackerExample.release.xcconfig"; path = "Target Support Files/Pods-RntrackerExample/Pods-RntrackerExample.release.xcconfig"; sourceTree = "<group>"; };
B3398286B848055D501F1B7C /* Pods-RntrackerExample-RntrackerExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RntrackerExample-RntrackerExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-RntrackerExample-RntrackerExampleTests/Pods-RntrackerExample-RntrackerExampleTests.debug.xcconfig"; sourceTree = "<group>"; };
C668E804196FA22A7C3B2356 /* Pods-RntrackerExample-RntrackerExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RntrackerExample-RntrackerExampleTests.release.xcconfig"; path = "Target Support Files/Pods-RntrackerExample-RntrackerExampleTests/Pods-RntrackerExample-RntrackerExampleTests.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
EDE6F6808B65834562C20EBE /* Pods-RntrackerExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RntrackerExample.debug.xcconfig"; path = "Target Support Files/Pods-RntrackerExample/Pods-RntrackerExample.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
97DF8FD3FE651CA8010130D7 /* Pods_RntrackerExample_RntrackerExampleTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
60ADCB349F6D4931641744DE /* Pods_RntrackerExample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
00E356EF1AD99517003FC87E /* RntrackerExampleTests */ = {
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* RntrackerExampleTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = RntrackerExampleTests;
sourceTree = "<group>";
};
00E356F01AD99517003FC87E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00E356F11AD99517003FC87E /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
13B07FAE1A68108700A75B9A /* RntrackerExample */ = {
isa = PBXGroup;
children = (
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
);
name = RntrackerExample;
sourceTree = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
5EB70A6C522028E464541BDD /* Pods_RntrackerExample.framework */,
2D22699E0FFA1C22F7AE3448 /* Pods_RntrackerExample_RntrackerExampleTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* RntrackerExample */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* RntrackerExampleTests */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
BBD78D7AC51CEA395F1C20DB /* Pods */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* RntrackerExample.app */,
00E356EE1AD99517003FC87E /* RntrackerExampleTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
EDE6F6808B65834562C20EBE /* Pods-RntrackerExample.debug.xcconfig */,
9473576378E546844F1249CB /* Pods-RntrackerExample.release.xcconfig */,
B3398286B848055D501F1B7C /* Pods-RntrackerExample-RntrackerExampleTests.debug.xcconfig */,
C668E804196FA22A7C3B2356 /* Pods-RntrackerExample-RntrackerExampleTests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* RntrackerExampleTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "RntrackerExampleTests" */;
buildPhases = (
C3DF83E17741CA620498EBE1 /* [CP] Check Pods Manifest.lock */,
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
81B05CC246621391DA66D200 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = RntrackerExampleTests;
productName = RntrackerExampleTests;
productReference = 00E356EE1AD99517003FC87E /* RntrackerExampleTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* RntrackerExample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RntrackerExample" */;
buildPhases = (
A6D6377E2D3186BB6736D56A /* [CP] Check Pods Manifest.lock */,
FD10A7F022414F080027D42C /* Start Packager */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
BA34C2DCDBAC12D7C0BE1BE6 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = RntrackerExample;
productName = RntrackerExample;
productReference = 13B07F961A680F5B00A75B9A /* RntrackerExample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "RntrackerExample" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* RntrackerExample */,
00E356ED1AD99517003FC87E /* RntrackerExampleTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$(SRCROOT)/.xcode.env.local",
"$(SRCROOT)/.xcode.env",
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
81B05CC246621391DA66D200 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-RntrackerExample-RntrackerExampleTests/Pods-RntrackerExample-RntrackerExampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-RntrackerExample-RntrackerExampleTests/Pods-RntrackerExample-RntrackerExampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RntrackerExample-RntrackerExampleTests/Pods-RntrackerExample-RntrackerExampleTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A6D6377E2D3186BB6736D56A /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RntrackerExample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BA34C2DCDBAC12D7C0BE1BE6 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-RntrackerExample/Pods-RntrackerExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-RntrackerExample/Pods-RntrackerExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RntrackerExample/Pods-RntrackerExample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C3DF83E17741CA620498EBE1 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RntrackerExample-RntrackerExampleTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FD10A7F022414F080027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* RntrackerExampleTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* RntrackerExample */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B3398286B848055D501F1B7C /* Pods-RntrackerExample-RntrackerExampleTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = RntrackerExampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RntrackerExample.app/RntrackerExample";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C668E804196FA22A7C3B2356 /* Pods-RntrackerExample-RntrackerExampleTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = RntrackerExampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RntrackerExample.app/RntrackerExample";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = EDE6F6808B65834562C20EBE /* Pods-RntrackerExample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = RntrackerExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = RntrackerExample;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9473576378E546844F1249CB /* Pods-RntrackerExample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = RntrackerExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = RntrackerExample;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "RntrackerExampleTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RntrackerExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "RntrackerExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "RntrackerExample.app"
BlueprintName = "RntrackerExample"
ReferencedContainer = "container:RntrackerExample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "RntrackerExampleTests.xctest"
BlueprintName = "RntrackerExampleTests"
ReferencedContainer = "container:RntrackerExample.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "RntrackerExample.app"
BlueprintName = "RntrackerExample"
ReferencedContainer = "container:RntrackerExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "RntrackerExample.app"
BlueprintName = "RntrackerExample"
ReferencedContainer = "container:RntrackerExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:RntrackerExample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,6 @@
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : RCTAppDelegate
@end

View file

@ -0,0 +1,26 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"RntrackerExample";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
@end

View file

@ -0,0 +1,53 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>RntrackerExample</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="RntrackerExample" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
<rect key="frame" x="0.0" y="626" width="375" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="bottom" secondItem="MN2-I3-ftu" secondAttribute="bottom" constant="20" id="OZV-Vh-mqD"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="akx-eg-2ui"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" id="i1E-0Y-4RG"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="52.173913043478265" y="375"/>
</scene>
</scenes>
</document>

Some files were not shown because too many files have changed in this diff Show more