diff --git a/Examples/apps/BasicExample/BasicExample.xcodeproj/project.pbxproj b/Examples/apps/BasicExample/BasicExample.xcodeproj/project.pbxproj index b0d77e27..95687aa1 100644 --- a/Examples/apps/BasicExample/BasicExample.xcodeproj/project.pbxproj +++ b/Examples/apps/BasicExample/BasicExample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 465879BD268A511600180335 /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879BC268A511600180335 /* ConsoleLogger.swift */; }; 469F7AF9265C25890038E773 /* EventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469F7AF8265C25890038E773 /* EventData.swift */; }; 46E38365265837EA00BA2502 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46E38364265837EA00BA2502 /* AppDelegate.swift */; }; 46E38367265837EA00BA2502 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46E38366265837EA00BA2502 /* SceneDelegate.swift */; }; @@ -19,6 +20,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 465879BC268A511600180335 /* ConsoleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConsoleLogger.swift; path = ../../../other_plugins/ConsoleLogger.swift; sourceTree = ""; }; 4663C72A267A8D6B00ADDD1A /* BasicExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BasicExample.entitlements; sourceTree = ""; }; 469F7AF8265C25890038E773 /* EventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventData.swift; sourceTree = ""; }; 46E38361265837EA00BA2502 /* BasicExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BasicExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -66,6 +68,7 @@ children = ( 4663C72A267A8D6B00ADDD1A /* BasicExample.entitlements */, 46E38364265837EA00BA2502 /* AppDelegate.swift */, + 465879BC268A511600180335 /* ConsoleLogger.swift */, 46E38366265837EA00BA2502 /* SceneDelegate.swift */, 469F7AF8265C25890038E773 /* EventData.swift */, 46E38368265837EA00BA2502 /* ViewController.swift */, @@ -164,6 +167,7 @@ 46E38369265837EA00BA2502 /* ViewController.swift in Sources */, 46E38365265837EA00BA2502 /* AppDelegate.swift in Sources */, 469F7AF9265C25890038E773 /* EventData.swift in Sources */, + 465879BD268A511600180335 /* ConsoleLogger.swift in Sources */, 46E38367265837EA00BA2502 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Examples/apps/BasicExample/BasicExample/AppDelegate.swift b/Examples/apps/BasicExample/BasicExample/AppDelegate.swift index ce56765d..86599f93 100644 --- a/Examples/apps/BasicExample/BasicExample/AppDelegate.swift +++ b/Examples/apps/BasicExample/BasicExample/AppDelegate.swift @@ -21,6 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .flushInterval(10) analytics = Analytics(configuration: configuration) + analytics?.add(plugin: ConsoleLogger(name: "consoleLogger")) return true } diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d06b66af --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,81 @@ +{ + "images" : [ + { + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit App/Base.lproj/Interface.storyboard b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Base.lproj/Interface.storyboard new file mode 100644 index 00000000..96cdcc63 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Base.lproj/Interface.storyboard @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit App/Info.plist b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Info.plist new file mode 100644 index 00000000..70432830 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit App/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + watchOSExample WatchKit App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + WKWatchKitApp + + + diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json new file mode 100644 index 00000000..ed7de25e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json new file mode 100644 index 00000000..e8b3252e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json @@ -0,0 +1,53 @@ +{ + "assets" : [ + { + "filename" : "Circular.imageset", + "idiom" : "watch", + "role" : "circular" + }, + { + "filename" : "Extra Large.imageset", + "idiom" : "watch", + "role" : "extra-large" + }, + { + "filename" : "Graphic Bezel.imageset", + "idiom" : "watch", + "role" : "graphic-bezel" + }, + { + "filename" : "Graphic Circular.imageset", + "idiom" : "watch", + "role" : "graphic-circular" + }, + { + "filename" : "Graphic Corner.imageset", + "idiom" : "watch", + "role" : "graphic-corner" + }, + { + "filename" : "Graphic Extra Large.imageset", + "idiom" : "watch", + "role" : "graphic-extra-large" + }, + { + "filename" : "Graphic Large Rectangular.imageset", + "idiom" : "watch", + "role" : "graphic-large-rectangular" + }, + { + "filename" : "Modular.imageset", + "idiom" : "watch", + "role" : "modular" + }, + { + "filename" : "Utilitarian.imageset", + "idiom" : "watch", + "role" : "utilitarian" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json new file mode 100644 index 00000000..ed7de25e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json new file mode 100644 index 00000000..9685a7fb --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json new file mode 100644 index 00000000..9685a7fb --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json new file mode 100644 index 00000000..9685a7fb --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json new file mode 100644 index 00000000..ed7de25e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json new file mode 100644 index 00000000..9685a7fb --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json new file mode 100644 index 00000000..ed7de25e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json new file mode 100644 index 00000000..ed7de25e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Contents.json b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ComplicationController.swift b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ComplicationController.swift new file mode 100644 index 00000000..8d35fb58 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ComplicationController.swift @@ -0,0 +1,59 @@ +// +// ComplicationController.swift +// watchOSExample WatchKit Extension +// +// Created by Brandon Sneed on 6/24/21. +// + +import ClockKit + + +class ComplicationController: NSObject, CLKComplicationDataSource { + + // MARK: - Complication Configuration + + func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) { + let descriptors = [ + CLKComplicationDescriptor(identifier: "complication", displayName: "watchOSExample", supportedFamilies: CLKComplicationFamily.allCases) + // Multiple complication support can be added here with more descriptors + ] + + // Call the handler with the currently supported complication descriptors + handler(descriptors) + } + + func handleSharedComplicationDescriptors(_ complicationDescriptors: [CLKComplicationDescriptor]) { + // Do any necessary work to support these newly shared complication descriptors + } + + // MARK: - Timeline Configuration + + func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) { + // Call the handler with the last entry date you can currently provide or nil if you can't support future timelines + handler(nil) + } + + func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) { + // Call the handler with your desired behavior when the device is locked + handler(.showOnLockScreen) + } + + // MARK: - Timeline Population + + func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) { + // Call the handler with the current timeline entry + handler(nil) + } + + func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) { + // Call the handler with the timeline entries after the given date + handler(nil) + } + + // MARK: - Sample Templates + + func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) { + // This method will be called once per supported complication, and the results will be cached + handler(nil) + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift new file mode 100644 index 00000000..90e2981e --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift @@ -0,0 +1,72 @@ +// +// ExtensionDelegate.swift +// watchOSExample WatchKit Extension +// +// Created by Brandon Sneed on 6/24/21. +// + +import WatchKit +import Segment + +class ExtensionDelegate: NSObject, WKExtensionDelegate { + var analytics: Analytics? = nil + + func applicationDidFinishLaunching() { + // Perform any final initialization of your application. + let configuration = Configuration(writeKey: "lAtKCqFrmtnhIVV7LDPTrgoCbL0ujlBe") + .trackApplicationLifecycleEvents(true) + .flushInterval(10) + + analytics = Analytics(configuration: configuration) + analytics?.add(plugin: ConsoleLogger(name: "consoleLogger")) + } + + func applicationDidBecomeActive() { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillResignActive() { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, etc. + } + + func handle(_ backgroundTasks: Set) { + // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. + for task in backgroundTasks { + // Use a switch statement to check the task type + switch task { + case let backgroundTask as WKApplicationRefreshBackgroundTask: + // Be sure to complete the background task once you’re done. + backgroundTask.setTaskCompletedWithSnapshot(false) + case let snapshotTask as WKSnapshotRefreshBackgroundTask: + // Snapshot tasks have a unique completion call, make sure to set your expiration date + snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil) + case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask: + // Be sure to complete the connectivity task once you’re done. + connectivityTask.setTaskCompletedWithSnapshot(false) + case let urlSessionTask as WKURLSessionRefreshBackgroundTask: + // Be sure to complete the URL session task once you’re done. + urlSessionTask.setTaskCompletedWithSnapshot(false) + case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask: + // Be sure to complete the relevant-shortcut task once you're done. + relevantShortcutTask.setTaskCompletedWithSnapshot(false) + case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask: + // Be sure to complete the intent-did-run task once you're done. + intentDidRunTask.setTaskCompletedWithSnapshot(false) + default: + // make sure to complete unhandled task types + task.setTaskCompletedWithSnapshot(false) + } + } + } + +} + +extension WKExtensionDelegate { + var analytics: Analytics? { + if let extDelegate = self as? ExtensionDelegate { + return extDelegate.analytics + } + return nil + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Info.plist b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Info.plist new file mode 100644 index 00000000..dd2038bd --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + watchOSExample WatchKit Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + CLKComplicationPrincipalClass + $(PRODUCT_MODULE_NAME).ComplicationController + NSExtension + + NSExtensionAttributes + + WKAppBundleIdentifier + com.segment.watchOSExample.watchkitapp + + NSExtensionPointIdentifier + com.apple.watchkit + + WKExtensionDelegateClassName + $(PRODUCT_MODULE_NAME).ExtensionDelegate + WKWatchOnly + + + diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/InterfaceController.swift b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/InterfaceController.swift new file mode 100644 index 00000000..d5f5932c --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/InterfaceController.swift @@ -0,0 +1,36 @@ +// +// InterfaceController.swift +// watchOSExample WatchKit Extension +// +// Created by Brandon Sneed on 6/24/21. +// + +import WatchKit +import Foundation +import Segment + + +class InterfaceController: WKInterfaceController { + var analytics = WKExtension.shared().delegate?.analytics + + override func awake(withContext context: Any?) { + // Configure interface objects here. + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + analytics?.screen(screenTitle: "Main Screen") + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + } + + @IBAction func trackTapped() { + analytics?.track(name: "track tapped") + } + + @IBAction func identifyTapped() { + analytics?.identify(userId: "watchPerson") + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/NotificationController.swift b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/NotificationController.swift new file mode 100644 index 00000000..b44b6b99 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/NotificationController.swift @@ -0,0 +1,34 @@ +// +// NotificationController.swift +// watchOSExample WatchKit Extension +// +// Created by Brandon Sneed on 6/24/21. +// + +import WatchKit +import Foundation +import UserNotifications + +class NotificationController: WKUserNotificationInterfaceController { + + override init() { + // Initialize variables here. + super.init() + + // Configure interface objects here. + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + } + + override func didReceive(_ notification: UNNotification) { + // This method is called when a notification needs to be presented. + // Implement it if you use a dynamic notification interface. + // Populate your dynamic notification interface as quickly as possible. + } +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/PushNotificationPayload.apns b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/PushNotificationPayload.apns new file mode 100644 index 00000000..c18b00ad --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/PushNotificationPayload.apns @@ -0,0 +1,20 @@ +{ + "aps": { + "alert": { + "body": "Test message", + "title": "Optional title", + "subtitle": "Optional subtitle" + }, + "category": "myCategory", + "thread-id": "5280" + }, + + "WatchKit Simulator Actions": [ + { + "title": "First Button", + "identifier": "firstButtonAction" + } + ], + + "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App." +} diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/SomeScreenController.swift b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/SomeScreenController.swift new file mode 100644 index 00000000..f230a27d --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/SomeScreenController.swift @@ -0,0 +1,27 @@ +// +// SomeScreenController.swift +// watchOSExample WatchKit Extension +// +// Created by Brandon Sneed on 6/25/21. +// + +import Foundation +import WatchKit + +class SomeScreenController: WKInterfaceController { + var analytics = WKExtension.shared().delegate?.analytics + + override func awake(withContext context: Any?) { + // Configure interface objects here. + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + analytics?.screen(screenTitle: "Some Screen Controller") + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + } + +} diff --git a/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ed5f0be0 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj @@ -0,0 +1,581 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 465879B22685058800180335 /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B12685058800180335 /* ConsoleLogger.swift */; }; + 465879B4268641B900180335 /* SomeScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B3268641B900180335 /* SomeScreenController.swift */; }; + 469ECD4D2684F9080028BE9A /* watchOSExample WatchKit App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 469ECD4C2684F9080028BE9A /* watchOSExample WatchKit App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 469ECD532684F9080028BE9A /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 469ECD512684F9080028BE9A /* Interface.storyboard */; }; + 469ECD552684F9090028BE9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 469ECD542684F9090028BE9A /* Assets.xcassets */; }; + 469ECD5C2684F9090028BE9A /* watchOSExample WatchKit Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 469ECD5B2684F9090028BE9A /* watchOSExample WatchKit Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 469ECD612684F9090028BE9A /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469ECD602684F9090028BE9A /* InterfaceController.swift */; }; + 469ECD632684F9090028BE9A /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469ECD622684F9090028BE9A /* ExtensionDelegate.swift */; }; + 469ECD652684F9090028BE9A /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469ECD642684F9090028BE9A /* NotificationController.swift */; }; + 469ECD672684F9090028BE9A /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469ECD662684F9090028BE9A /* ComplicationController.swift */; }; + 469ECD692684F90A0028BE9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 469ECD682684F90A0028BE9A /* Assets.xcassets */; }; + 469ECD7B2684FD710028BE9A /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = 469ECD7A2684FD710028BE9A /* Segment */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 469ECD4E2684F9080028BE9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 469ECD422684F9080028BE9A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 469ECD4B2684F9080028BE9A; + remoteInfo = "watchOSExample WatchKit App"; + }; + 469ECD5D2684F9090028BE9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 469ECD422684F9080028BE9A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 469ECD5A2684F9090028BE9A; + remoteInfo = "watchOSExample WatchKit Extension"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 469ECD712684F90A0028BE9A /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 469ECD5C2684F9090028BE9A /* watchOSExample WatchKit Extension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 469ECD752684F90A0028BE9A /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + 469ECD4D2684F9080028BE9A /* watchOSExample WatchKit App.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 465879B12685058800180335 /* ConsoleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConsoleLogger.swift; path = ../../../other_plugins/ConsoleLogger.swift; sourceTree = ""; }; + 465879B3268641B900180335 /* SomeScreenController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SomeScreenController.swift; sourceTree = ""; }; + 469ECD482684F9080028BE9A /* watchOSExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = watchOSExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 469ECD4C2684F9080028BE9A /* watchOSExample WatchKit App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "watchOSExample WatchKit App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 469ECD522684F9080028BE9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; + 469ECD542684F9090028BE9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 469ECD562684F9090028BE9A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 469ECD5B2684F9090028BE9A /* watchOSExample WatchKit Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "watchOSExample WatchKit Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 469ECD602684F9090028BE9A /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = ""; }; + 469ECD622684F9090028BE9A /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = ""; }; + 469ECD642684F9090028BE9A /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = ""; }; + 469ECD662684F9090028BE9A /* ComplicationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = ""; }; + 469ECD682684F90A0028BE9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 469ECD6A2684F90A0028BE9A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 469ECD6B2684F90A0028BE9A /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 469ECD582684F9090028BE9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 469ECD7B2684FD710028BE9A /* Segment in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 469ECD412684F9080028BE9A = { + isa = PBXGroup; + children = ( + 469ECD502684F9080028BE9A /* watchOSExample WatchKit App */, + 469ECD5F2684F9090028BE9A /* watchOSExample WatchKit Extension */, + 469ECD492684F9080028BE9A /* Products */, + 469ECD792684FD710028BE9A /* Frameworks */, + ); + sourceTree = ""; + }; + 469ECD492684F9080028BE9A /* Products */ = { + isa = PBXGroup; + children = ( + 469ECD482684F9080028BE9A /* watchOSExample.app */, + 469ECD4C2684F9080028BE9A /* watchOSExample WatchKit App.app */, + 469ECD5B2684F9090028BE9A /* watchOSExample WatchKit Extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 469ECD502684F9080028BE9A /* watchOSExample WatchKit App */ = { + isa = PBXGroup; + children = ( + 469ECD512684F9080028BE9A /* Interface.storyboard */, + 469ECD542684F9090028BE9A /* Assets.xcassets */, + 469ECD562684F9090028BE9A /* Info.plist */, + ); + path = "watchOSExample WatchKit App"; + sourceTree = ""; + }; + 469ECD5F2684F9090028BE9A /* watchOSExample WatchKit Extension */ = { + isa = PBXGroup; + children = ( + 465879B12685058800180335 /* ConsoleLogger.swift */, + 469ECD602684F9090028BE9A /* InterfaceController.swift */, + 465879B3268641B900180335 /* SomeScreenController.swift */, + 469ECD622684F9090028BE9A /* ExtensionDelegate.swift */, + 469ECD642684F9090028BE9A /* NotificationController.swift */, + 469ECD662684F9090028BE9A /* ComplicationController.swift */, + 469ECD682684F90A0028BE9A /* Assets.xcassets */, + 469ECD6A2684F90A0028BE9A /* Info.plist */, + 469ECD6B2684F90A0028BE9A /* PushNotificationPayload.apns */, + ); + path = "watchOSExample WatchKit Extension"; + sourceTree = ""; + }; + 469ECD792684FD710028BE9A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 469ECD472684F9080028BE9A /* watchOSExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 469ECD762684F90A0028BE9A /* Build configuration list for PBXNativeTarget "watchOSExample" */; + buildPhases = ( + 469ECD462684F9080028BE9A /* Resources */, + 469ECD752684F90A0028BE9A /* Embed Watch Content */, + ); + buildRules = ( + ); + dependencies = ( + 469ECD4F2684F9080028BE9A /* PBXTargetDependency */, + ); + name = watchOSExample; + productName = watchOSExample; + productReference = 469ECD482684F9080028BE9A /* watchOSExample.app */; + productType = "com.apple.product-type.application.watchapp2-container"; + }; + 469ECD4B2684F9080028BE9A /* watchOSExample WatchKit App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 469ECD722684F90A0028BE9A /* Build configuration list for PBXNativeTarget "watchOSExample WatchKit App" */; + buildPhases = ( + 469ECD4A2684F9080028BE9A /* Resources */, + 469ECD712684F90A0028BE9A /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 469ECD5E2684F9090028BE9A /* PBXTargetDependency */, + ); + name = "watchOSExample WatchKit App"; + productName = "watchOSExample WatchKit App"; + productReference = 469ECD4C2684F9080028BE9A /* watchOSExample WatchKit App.app */; + productType = "com.apple.product-type.application.watchapp2"; + }; + 469ECD5A2684F9090028BE9A /* watchOSExample WatchKit Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 469ECD6E2684F90A0028BE9A /* Build configuration list for PBXNativeTarget "watchOSExample WatchKit Extension" */; + buildPhases = ( + 469ECD572684F9090028BE9A /* Sources */, + 469ECD582684F9090028BE9A /* Frameworks */, + 469ECD592684F9090028BE9A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "watchOSExample WatchKit Extension"; + packageProductDependencies = ( + 469ECD7A2684FD710028BE9A /* Segment */, + ); + productName = "watchOSExample WatchKit Extension"; + productReference = 469ECD5B2684F9090028BE9A /* watchOSExample WatchKit Extension.appex */; + productType = "com.apple.product-type.watchkit2-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 469ECD422684F9080028BE9A /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1250; + TargetAttributes = { + 469ECD472684F9080028BE9A = { + CreatedOnToolsVersion = 12.5.1; + }; + 469ECD4B2684F9080028BE9A = { + CreatedOnToolsVersion = 12.5.1; + }; + 469ECD5A2684F9090028BE9A = { + CreatedOnToolsVersion = 12.5.1; + }; + }; + }; + buildConfigurationList = 469ECD452684F9080028BE9A /* Build configuration list for PBXProject "watchOSExample" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 469ECD412684F9080028BE9A; + productRefGroup = 469ECD492684F9080028BE9A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 469ECD472684F9080028BE9A /* watchOSExample */, + 469ECD4B2684F9080028BE9A /* watchOSExample WatchKit App */, + 469ECD5A2684F9090028BE9A /* watchOSExample WatchKit Extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 469ECD462684F9080028BE9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 469ECD4A2684F9080028BE9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 469ECD552684F9090028BE9A /* Assets.xcassets in Resources */, + 469ECD532684F9080028BE9A /* Interface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 469ECD592684F9090028BE9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 469ECD692684F90A0028BE9A /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 469ECD572684F9090028BE9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 469ECD652684F9090028BE9A /* NotificationController.swift in Sources */, + 465879B4268641B900180335 /* SomeScreenController.swift in Sources */, + 469ECD672684F9090028BE9A /* ComplicationController.swift in Sources */, + 469ECD632684F9090028BE9A /* ExtensionDelegate.swift in Sources */, + 465879B22685058800180335 /* ConsoleLogger.swift in Sources */, + 469ECD612684F9090028BE9A /* InterfaceController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 469ECD4F2684F9080028BE9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 469ECD4B2684F9080028BE9A /* watchOSExample WatchKit App */; + targetProxy = 469ECD4E2684F9080028BE9A /* PBXContainerItemProxy */; + }; + 469ECD5E2684F9090028BE9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 469ECD5A2684F9090028BE9A /* watchOSExample WatchKit Extension */; + targetProxy = 469ECD5D2684F9090028BE9A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 469ECD512684F9080028BE9A /* Interface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 469ECD522684F9080028BE9A /* Base */, + ); + name = Interface.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 469ECD6C2684F90A0028BE9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 469ECD6D2684F90A0028BE9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 469ECD6F2684F90A0028BE9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "watchOSExample WatchKit Extension/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.segment.watchOSExample.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 7.4; + }; + name = Debug; + }; + 469ECD702684F90A0028BE9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "watchOSExample WatchKit Extension/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.segment.watchOSExample.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 7.4; + }; + name = Release; + }; + 469ECD732684F90A0028BE9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + IBSC_MODULE = watchOSExample_WatchKit_Extension; + INFOPLIST_FILE = "watchOSExample WatchKit App/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.segment.watchOSExample.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 7.4; + }; + name = Debug; + }; + 469ECD742684F90A0028BE9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + IBSC_MODULE = watchOSExample_WatchKit_Extension; + INFOPLIST_FILE = "watchOSExample WatchKit App/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.segment.watchOSExample.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 7.4; + }; + name = Release; + }; + 469ECD772684F90A0028BE9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.segment.watchOSExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 469ECD782684F90A0028BE9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.segment.watchOSExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 469ECD452684F9080028BE9A /* Build configuration list for PBXProject "watchOSExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 469ECD6C2684F90A0028BE9A /* Debug */, + 469ECD6D2684F90A0028BE9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 469ECD6E2684F90A0028BE9A /* Build configuration list for PBXNativeTarget "watchOSExample WatchKit Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 469ECD6F2684F90A0028BE9A /* Debug */, + 469ECD702684F90A0028BE9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 469ECD722684F90A0028BE9A /* Build configuration list for PBXNativeTarget "watchOSExample WatchKit App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 469ECD732684F90A0028BE9A /* Debug */, + 469ECD742684F90A0028BE9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 469ECD762684F90A0028BE9A /* Build configuration list for PBXNativeTarget "watchOSExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 469ECD772684F90A0028BE9A /* Debug */, + 469ECD782684F90A0028BE9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 469ECD7A2684FD710028BE9A /* Segment */ = { + isa = XCSwiftPackageProductDependency; + productName = Segment; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 469ECD422684F9080028BE9A /* Project object */; +} diff --git a/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Examples/apps/watchOSExample/watchOSExample.xcworkspace/contents.xcworkspacedata b/Examples/apps/watchOSExample/watchOSExample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..db5608a4 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Examples/apps/watchOSExample/watchOSExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/apps/watchOSExample/watchOSExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Examples/apps/watchOSExample/watchOSExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Package.swift b/Package.swift index e4657137..497f65cb 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .macOS("10.15"), .iOS("13.0"), .tvOS("11.0"), - .watchOS("6.2") + .watchOS("7.1") ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. diff --git a/Segment.xcodeproj/project.pbxproj b/Segment.xcodeproj/project.pbxproj index 2d05a971..30c218ce 100644 --- a/Segment.xcodeproj/project.pbxproj +++ b/Segment.xcodeproj/project.pbxproj @@ -31,6 +31,9 @@ 46210811260538BE00EBC4A8 /* KeyPath_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46210810260538BE00EBC4A8 /* KeyPath_Tests.swift */; }; 46210836260BBEE400EBC4A8 /* DeviceToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46210835260BBEE400EBC4A8 /* DeviceToken.swift */; }; 4658175425BA4C20006B2809 /* HTTPClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4658175325BA4C20006B2809 /* HTTPClient_Tests.swift */; }; + 465879B72686554400180335 /* EventsObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B62686554400180335 /* EventsObjC.swift */; }; + 465879BA2686560C00180335 /* watchOSDelegation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B82686560C00180335 /* watchOSDelegation.swift */; }; + 465879BB2686560C00180335 /* watchOSLifecycleMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B92686560C00180335 /* watchOSLifecycleMonitor.swift */; }; 4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4663C728267A799100ADDD1A /* QueueTimer.swift */; }; 46A018C225E5857D00F9CCD8 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018C125E5857D00F9CCD8 /* Context.swift */; }; 46A018D425E6C9C200F9CCD8 /* LinuxUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */; }; @@ -49,7 +52,6 @@ 967C40E3258D4DAF008EB0B6 /* Metrics_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40E2258D4DAF008EB0B6 /* Metrics_Tests.swift */; }; 9692724E25A4E5B7009B5298 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9692724D25A4E5B7009B5298 /* Startup.swift */; }; 9692726825A583A6009B5298 /* SegmentDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9692726725A583A6009B5298 /* SegmentDestination.swift */; }; - 969A534325B10324009227D9 /* iOSAppBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969A534225B10324009227D9 /* iOSAppBackground.swift */; }; 96C33A9C25880A5E00F3D538 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33A9B25880A5E00F3D538 /* Logger.swift */; }; 96C33AAC25892D6D00F3D538 /* Metrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33AAB25892D6D00F3D538 /* Metrics.swift */; }; 96C33AB1258961F500F3D538 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33AB0258961F500F3D538 /* Settings.swift */; }; @@ -110,6 +112,9 @@ 46210810260538BE00EBC4A8 /* KeyPath_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPath_Tests.swift; sourceTree = ""; }; 46210835260BBEE400EBC4A8 /* DeviceToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceToken.swift; sourceTree = ""; }; 4658175325BA4C20006B2809 /* HTTPClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient_Tests.swift; sourceTree = ""; }; + 465879B62686554400180335 /* EventsObjC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsObjC.swift; sourceTree = ""; }; + 465879B82686560C00180335 /* watchOSDelegation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = watchOSDelegation.swift; sourceTree = ""; }; + 465879B92686560C00180335 /* watchOSLifecycleMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = watchOSLifecycleMonitor.swift; sourceTree = ""; }; 4663C728267A799100ADDD1A /* QueueTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTimer.swift; sourceTree = ""; }; 46A018C125E5857D00F9CCD8 /* Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; }; 46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinuxUtils.swift; sourceTree = ""; }; @@ -129,7 +134,6 @@ 967C40ED259A7311008EB0B6 /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 9692724D25A4E5B7009B5298 /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; 9692726725A583A6009B5298 /* SegmentDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentDestination.swift; sourceTree = ""; }; - 969A534225B10324009227D9 /* iOSAppBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSAppBackground.swift; sourceTree = ""; }; 96C33A9B25880A5E00F3D538 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 96C33AAB25892D6D00F3D538 /* Metrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metrics.swift; sourceTree = ""; }; 96C33AB0258961F500F3D538 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; @@ -175,6 +179,8 @@ 460227402612984100A9E913 /* watchOS */ = { isa = PBXGroup; children = ( + 465879B82686560C00180335 /* watchOSDelegation.swift */, + 465879B92686560C00180335 /* watchOSLifecycleMonitor.swift */, 460227412612987300A9E913 /* watchOSLifecycleEvents.swift */, ); path = watchOS; @@ -188,6 +194,14 @@ path = Support; sourceTree = ""; }; + 465879B52686554400180335 /* ObjC */ = { + isa = PBXGroup; + children = ( + 465879B62686554400180335 /* EventsObjC.swift */, + ); + path = ObjC; + sourceTree = ""; + }; 46A018D825E97FCB00F9CCD8 /* Vendors */ = { isa = PBXGroup; children = ( @@ -236,7 +250,6 @@ isa = PBXGroup; children = ( 9620864F257AA83E00314F8D /* iOSLifecycleMonitor.swift */, - 969A534225B10324009227D9 /* iOSAppBackground.swift */, 46022763261E64A800A9E913 /* iOSLifecycleEvents.swift */, 4602276B261E7BF900A9E913 /* iOSDelegation.swift */, ); @@ -331,6 +344,7 @@ OBJ_8 /* Segment */ = { isa = PBXGroup; children = ( + 465879B52686554400180335 /* ObjC */, 9620864E257AA82900314F8D /* Plugins */, A31A16A325780A8D00C9CDDF /* Utilities */, OBJ_9 /* Analytics.swift */, @@ -411,7 +425,7 @@ attributes = { LastSwiftMigration = 9999; LastSwiftUpdateCheck = 1220; - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1250; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Segment" */; compatibilityVersion = "Xcode 3.2"; @@ -445,11 +459,11 @@ A3AEE14A2580208E002386EB /* iso8601.swift in Sources */, 46A018EE25E9A74F00F9CCD8 /* VendorSystem.swift in Sources */, 46FE4CE025A53FAD003A7362 /* Storage.swift in Sources */, + 465879BA2686560C00180335 /* watchOSDelegation.swift in Sources */, 46022764261E64A800A9E913 /* iOSLifecycleEvents.swift in Sources */, 4621080C2605332D00EBC4A8 /* KeyPath.swift in Sources */, A31A16262576B6F200C9CDDF /* Timeline.swift in Sources */, 96C33AB1258961F500F3D538 /* Settings.swift in Sources */, - 969A534325B10324009227D9 /* iOSAppBackground.swift in Sources */, 46E382E72654429A00BA2502 /* Utils.swift in Sources */, A31A16B225781CB400C9CDDF /* JSON.swift in Sources */, 46022771261F7A4800A9E913 /* Atomic.swift in Sources */, @@ -460,6 +474,7 @@ A31A16E12579779600C9CDDF /* Version.swift in Sources */, 46210836260BBEE400EBC4A8 /* DeviceToken.swift in Sources */, 9692724E25A4E5B7009B5298 /* Startup.swift in Sources */, + 465879B72686554400180335 /* EventsObjC.swift in Sources */, 4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */, 46FE4C9C25A3F41C003A7362 /* LinuxLifecycleMonitor.swift in Sources */, 460227422612987300A9E913 /* watchOSLifecycleEvents.swift in Sources */, @@ -477,6 +492,7 @@ 96208650257AA83E00314F8D /* iOSLifecycleMonitor.swift in Sources */, 96C33AAC25892D6D00F3D538 /* Metrics.swift in Sources */, 46031D65266E7C10009BA540 /* StartupQueue.swift in Sources */, + 465879BB2686560C00180335 /* watchOSLifecycleMonitor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -530,7 +546,6 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Segment.xcodeproj/Segment_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", @@ -547,8 +562,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGET_NAME = Segment; - TVOS_DEPLOYMENT_TARGET = 11.0; - WATCHOS_DEPLOYMENT_TARGET = 6.2; }; name = Debug; }; @@ -562,7 +575,6 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Segment.xcodeproj/Segment_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", @@ -579,8 +591,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGET_NAME = Segment; - TVOS_DEPLOYMENT_TARGET = 11.0; - WATCHOS_DEPLOYMENT_TARGET = 6.2; }; name = Release; }; @@ -595,7 +605,6 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Segment.xcodeproj/Segment_Tests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", @@ -608,8 +617,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGET_NAME = "Segment-Tests"; - TVOS_DEPLOYMENT_TARGET = 11.0; - WATCHOS_DEPLOYMENT_TARGET = 6.2; }; name = Debug; }; @@ -624,7 +631,6 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Segment.xcodeproj/Segment_Tests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", @@ -637,8 +643,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGET_NAME = "Segment-Tests"; - TVOS_DEPLOYMENT_TARGET = 11.0; - WATCHOS_DEPLOYMENT_TARGET = 6.2; }; name = Release; }; @@ -684,6 +688,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; @@ -693,7 +698,9 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 11.0; USE_HEADERMAP = NO; + WATCHOS_DEPLOYMENT_TARGET = 7.1; }; name = Debug; }; @@ -754,6 +761,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -763,7 +771,9 @@ SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 11.0; USE_HEADERMAP = NO; + WATCHOS_DEPLOYMENT_TARGET = 7.1; }; name = Release; }; diff --git a/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme b/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme index 51510d09..e4ef2220 100644 --- a/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme +++ b/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme @@ -1,6 +1,6 @@ 0 { - context["locale"] = Locale.preferredLanguages[0] - } - // timezone - context["timezone"] = TimeZone.current.identifier -} - -internal func insertDynamicPlatformContextData(context: inout [String: Any]) { - // network - let status = connectionStatus() - - var cellular = false - var wifi = false - - switch status { - case .online(.cellular): - cellular = true - case .online(.wifi): - wifi = true - default: - break - } - - context["network"] = [ - "bluetooth": false, // ios doesn't really support bluetooth network, does it?? - "cellular": cellular, - "wifi": wifi - ] - - // other stuff?? ... -} - -private func deviceModel() -> String { - var systemInfo = utsname() - uname(&systemInfo) - let machineMirror = Mirror(reflecting: systemInfo.machine) - let identifier = machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { return identifier } - return identifier + String(UnicodeScalar(UInt8(value))) - } - return identifier -} - -func macAddress(bsd : String) -> String? -{ - let MAC_ADDRESS_LENGTH = 6 - let separator = ":" - - var length : size_t = 0 - var buffer : [CChar] - - let bsdIndex = Int32(if_nametoindex(bsd)) - if bsdIndex == 0 { - return nil - } - let bsdData = Data(bsd.utf8) - var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex] - - if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 { - return nil; - } - - buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in - for x in 0...stride + 1 - let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)! - let lower = rangeOfToken.upperBound - let upper = lower + MAC_ADDRESS_LENGTH - let macAddressData = infoData[lower.. String { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + return identifier + } + + private func macAddress(bsd : String) -> String? { + let MAC_ADDRESS_LENGTH = 6 + let separator = ":" + + var length : size_t = 0 + var buffer : [CChar] + + let bsdIndex = Int32(if_nametoindex(bsd)) + if bsdIndex == 0 { + return nil + } + let bsdData = Data(bsd.utf8) + var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex] + + if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 { + return nil; + } + + buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in + for x in 0...stride + 1 + let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)! + let lower = rangeOfToken.upperBound + let upper = lower + MAC_ADDRESS_LENGTH + let macAddressData = infoData[lower.. 0 { - context["locale"] = Locale.preferredLanguages[0] - } - // timezone - context["timezone"] = TimeZone.current.identifier -} - -internal func insertDynamicPlatformContextData(context: inout [String: Any]) { - // network - let status = connectionStatus() - - var cellular = false - var wifi = false - - switch status { - case .online(.cellular): - cellular = true - case .online(.wifi): - wifi = true - default: - break - } - - context["network"] = [ - "bluetooth": false, // ios doesn't really support bluetooth network, does it?? - "cellular": cellular, - "wifi": wifi - ] - - // other stuff?? ... -} - -#endif diff --git a/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift b/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift new file mode 100644 index 00000000..895a5ee6 --- /dev/null +++ b/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift @@ -0,0 +1,56 @@ +// +// File.swift +// +// +// Created by Brandon Sneed on 6/24/21. +// + +#if os(watchOS) + +import Foundation +import WatchKit + +// MARK: - Remote Notifications + +public protocol RemoteNotifications: Plugin { + func registeredForRemoteNotifications(deviceToken: Data) + func failedToRegisterForRemoteNotification(error: Error?) + func receivedRemoteNotification(userInfo: [String: Any]) +} + +extension RemoteNotifications { + public func registeredForRemoteNotifications(deviceToken: Data) {} + public func failedToRegisterForRemoteNotification(error: Error?) {} + public func receivedRemoteNotification(userInfo: [String: Any]) {} +} + +extension Analytics { + public func registeredForRemoteNotifications(deviceToken: Data) { + setDeviceToken(deviceToken.hexString) + + apply { plugin in + if let p = plugin as? RemoteNotifications { + p.registeredForRemoteNotifications(deviceToken: deviceToken) + } + } + } + + public func failedToRegisterForRemoteNotification(error: Error?) { + apply { plugin in + if let p = plugin as? RemoteNotifications { + p.failedToRegisterForRemoteNotification(error: error) + } + } + } + + public func receivedRemoteNotification(userInfo: [String: Any]) { + apply { plugin in + if let p = plugin as? RemoteNotifications { + p.receivedRemoteNotification(userInfo: userInfo) + } + } + } + +} + +#endif diff --git a/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleEvents.swift b/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleEvents.swift index aa715149..a8f93110 100644 --- a/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleEvents.swift +++ b/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleEvents.swift @@ -5,25 +5,75 @@ // Created by Brandon Sneed on 3/29/21. // -import Foundation - #if os(watchOS) -// Work in progress ... TBD +import Foundation +import WatchKit -class watchOSLifecycleMonitor: PlatformPlugin { - static var specificName: String = "Segment_watchOSLifecycleMonitor" +class watchOSLifecycleEvents: PlatformPlugin, watchOSLifecycle { + static var versionKey = "SEGVersionKey" + static var buildKey = "SEGBuildKeyV2" + + static var specificName: String = "Segment_watchOSLifecycleEvents" - var type: PluginType - var name: String + let type: PluginType + let name: String var analytics: Analytics? - required init(name: String) { - self.type = .utility + public required init(name: String) { self.name = name + self.type = .before } - // ... + func applicationDidFinishLaunching(watchExtension: WKExtension) { + if analytics?.configuration.values.trackApplicationLifecycleEvents == false { + return + } + + let previousVersion = UserDefaults.standard.string(forKey: Self.versionKey) + let previousBuild = UserDefaults.standard.string(forKey: Self.buildKey) + + let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + let currentBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String + + if previousBuild == nil { + analytics?.track(name: "Application Installed", properties: [ + "version": currentVersion ?? "", + "build": currentBuild ?? "" + ]) + } else if currentBuild != previousBuild { + analytics?.track(name: "Application Updated", properties: [ + "previous_version": previousVersion ?? "", + "previous_build": previousBuild ?? "", + "version": currentVersion ?? "", + "build": currentBuild ?? "" + ]) + } + + analytics?.track(name: "Application Opened", properties: [ + "from_background": false, + "version": currentVersion ?? "", + "build": currentBuild ?? "" + ]) + + UserDefaults.standard.setValue(currentVersion, forKey: Self.versionKey) + UserDefaults.standard.setValue(currentBuild, forKey: Self.buildKey) + } + + func applicationWillEnterForeground(watchExtension: WKExtension) { + if analytics?.configuration.values.trackApplicationLifecycleEvents == false { + return + } + + let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + let currentBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String + + analytics?.track(name: "Application Opened", properties: [ + "from_background": true, + "version": currentVersion ?? "", + "build": currentBuild ?? "" + ]) + } } #endif diff --git a/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift b/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift new file mode 100644 index 00000000..c98057bc --- /dev/null +++ b/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift @@ -0,0 +1,141 @@ +// +// File.swift +// +// +// Created by Brandon Sneed on 6/24/21. +// + +#if os(watchOS) + +import Foundation +import WatchKit + +public protocol watchOSLifecycle { + func applicationDidFinishLaunching(watchExtension: WKExtension) + func applicationWillEnterForeground(watchExtension: WKExtension) + func applicationDidEnterBackground(watchExtension: WKExtension) + func applicationDidBecomeActive(watchExtension: WKExtension) + func applicationWillResignActive(watchExtension: WKExtension) +} + +public extension watchOSLifecycle { + func applicationDidFinishLaunching(watchExtension: WKExtension) { } + func applicationWillEnterForeground(watchExtension: WKExtension) { } + func applicationDidEnterBackground(watchExtension: WKExtension) { } + func applicationDidBecomeActive(watchExtension: WKExtension) { } + func applicationWillResignActive(watchExtension: WKExtension) { } +} + + +class watchOSLifecycleMonitor: PlatformPlugin { + static var specificName: String = "Segment_watchOSLifecycleMonitor" + + var type: PluginType + var name: String + var analytics: Analytics? + var wasBackgrounded: Bool = false + + private var watchExtension = WKExtension.shared() + private var appNotifications: [NSNotification.Name] = [WKExtension.applicationDidFinishLaunchingNotification, + WKExtension.applicationWillEnterForegroundNotification, + WKExtension.applicationDidEnterBackgroundNotification, + WKExtension.applicationDidBecomeActiveNotification, + WKExtension.applicationWillResignActiveNotification] + + required init(name: String) { + self.type = .utility + self.name = name + + watchExtension = WKExtension.shared() + + setupListeners() + } + + @objc + func notificationResponse(notification: NSNotification) { + switch (notification.name) { + case WKExtension.applicationDidFinishLaunchingNotification: + self.applicationDidFinishLaunching(notification: notification) + case WKExtension.applicationWillEnterForegroundNotification: + self.applicationWillEnterForeground(notification: notification) + case WKExtension.applicationDidEnterBackgroundNotification: + self.applicationDidEnterBackground(notification: notification) + case WKExtension.applicationDidBecomeActiveNotification: + self.applicationDidBecomeActive(notification: notification) + case WKExtension.applicationWillResignActiveNotification: + self.applicationWillResignActive(notification: notification) + default: + break + } + } + + func setupListeners() { + // Configure the current life cycle events + let notificationCenter = NotificationCenter.default + for notification in appNotifications { + notificationCenter.addObserver(self, selector: #selector(notificationResponse(notification:)), name: notification, object: nil) + } + } + + func applicationDidFinishLaunching(notification: NSNotification) { + analytics?.apply { (ext) in + if let validExt = ext as? watchOSLifecycle { + validExt.applicationDidFinishLaunching(watchExtension: watchExtension) + } + } + } + + func applicationWillEnterForeground(notification: NSNotification) { + // watchOS will receive this after didFinishLaunching, which is different + // from iOS, so ignore until we've been backgrounded at least once. + if wasBackgrounded == false { return } + + analytics?.apply { (ext) in + if let validExt = ext as? watchOSLifecycle { + validExt.applicationWillEnterForeground(watchExtension: watchExtension) + } + } + } + + func applicationDidEnterBackground(notification: NSNotification) { + // make sure to denote that we were backgrounded. + wasBackgrounded = true + + analytics?.apply { (ext) in + if let validExt = ext as? watchOSLifecycle { + validExt.applicationDidEnterBackground(watchExtension: watchExtension) + } + } + } + + func applicationDidBecomeActive(notification: NSNotification) { + analytics?.apply { (ext) in + if let validExt = ext as? watchOSLifecycle { + validExt.applicationDidBecomeActive(watchExtension: watchExtension) + } + } + } + + func applicationWillResignActive(notification: NSNotification) { + analytics?.apply { (ext) in + if let validExt = ext as? watchOSLifecycle { + validExt.applicationWillResignActive(watchExtension: watchExtension) + } + } + } + +} + +// MARK: - Segment Destination Extension + +extension SegmentDestination: watchOSLifecycle { + public func applicationWillEnterForeground(watchExtension: WKExtension) { + enterForeground() + } + + public func applicationDidEnterBackground(watchExtension: WKExtension) { + enterBackground() + } +} + +#endif diff --git a/Sources/Segment/Plugins/SegmentDestination.swift b/Sources/Segment/Plugins/SegmentDestination.swift index 873cdb1a..6fd79e70 100644 --- a/Sources/Segment/Plugins/SegmentDestination.swift +++ b/Sources/Segment/Plugins/SegmentDestination.swift @@ -7,6 +7,14 @@ import Foundation +#if os(Linux) +// Whoever is doing swift/linux development over there +// decided that it'd be a good idea to split out a TON +// of stuff into another framework that NO OTHER PLATFORM +// has; I guess to be special. :man-shrugging: +import FoundationNetworking +#endif + public class SegmentDestination: DestinationPlugin { public let type: PluginType = .destination public let name: String @@ -17,9 +25,17 @@ public class SegmentDestination: DestinationPlugin { } } + internal struct UploadTaskInfo { + let url: URL + let task: URLSessionDataTask + // set/used via an extension in iOSLifecycleMonitor.swift + typealias CleanupClosure = () -> Void + var taskID: Int = 0 + var cleanup: CleanupClosure? = nil + } + private var httpClient: HTTPClient? - private var pendingURLs = [URL]() - private var uploadInProgress = false + private var uploads = [UploadTaskInfo]() private var storage: Storage? private var maxPayloadSize = 500000 // Max 500kb @@ -83,6 +99,16 @@ public class SegmentDestination: DestinationPlugin { return event } + // MARK: - Abstracted Lifecycle Methods + internal func enterForeground() { + flushTimer?.resume() + } + + internal func enterBackground() { + flushTimer?.suspend() + flush() + } + // MARK: - Event Parsing Methods private func queueEvent(event: T) { guard let storage = self.storage else { return } @@ -105,48 +131,89 @@ public class SegmentDestination: DestinationPlugin { // Read events from file system guard let data = storage.read(Storage.Constants.events) else { return } - if !uploadInProgress { - uploadInProgress = true - var processedCall = [Bool]() - - var fileSizeTotal: Int64 = 0 + cleanupUploads() + + analytics.log(message: "Uploads in-progress: \(pendingUploads)") + + if pendingUploads == 0 { for url in data { - // Get the file size - do { - let attributes = try FileManager.default.attributesOfItem(atPath: url.absoluteString) - guard let fileSize = attributes[FileAttributeKey.size] as? Int64 else { - analytics.log(message: "File size could not be read") - return - } - fileSizeTotal += fileSize - } catch { - analytics.log(message: "Could not read file attributes") - } - - // Don't continue sending if the file size total has become too large - // send it off in the next flush. - if fileSizeTotal > maxPayloadSize { - analytics.log(message: "Batch file is too large to be sent") - break - } + analytics.log(message: "Processing Batch:\n\(url.lastPathComponent)") - httpClient.startBatchUpload(writeKey: analytics.configuration.values.writeKey, batch: url, completion: { [weak self] (succeeded) in - // Track that the call has finished - processedCall.append(succeeded) - - if succeeded { - // Remove events - storage.remove(file: url) - } else { - analytics.logFlush() - } + if isPayloadSizeAcceptable(url: url) { + let uploadTask = httpClient.startBatchUpload(writeKey: analytics.configuration.values.writeKey, batch: url) { (result) in + switch result { + case .success(_): + storage.remove(file: url) + default: + analytics.logFlush() + } - if processedCall.count == data.count { - self?.uploadInProgress = false + analytics.log(message: "Processed: \(url.lastPathComponent)") + } + // we have a legit upload in progress now, so add it to our list. + if let upload = uploadTask { + add(uploadTask: UploadTaskInfo(url: url, task: upload)) } - }) + } } + } else { + analytics.log(message: "Skipping processing; Uploads in progress.") } } } +// MARK: - Upload management + +extension SegmentDestination { + internal func cleanupUploads() { + // lets go through and get rid of any tasks that aren't running. + // either they were suspended because a background task took too + // long, or the os orphaned it due to device constraints (like a watch). + let before = uploads.count + var newPending = uploads + newPending.removeAll { uploadInfo in + let shouldRemove = uploadInfo.task.state != .running + if shouldRemove, let cleanup = uploadInfo.cleanup { + cleanup() + } + return shouldRemove + } + uploads = newPending + let after = uploads.count + analytics?.log(message: "Cleaned up \(before - after) non-running uploads.") + } + + internal var pendingUploads: Int { + return uploads.count + } + + internal func add(uploadTask: UploadTaskInfo) { + uploads.append(uploadTask) + } + + internal func isPayloadSizeAcceptable(url: URL) -> Bool { + var result = true + var fileSizeTotal: Int64 = 0 + + // Make sure we're under the max payload size. + do { + let attributes = try FileManager.default.attributesOfItem(atPath: url.path) + guard let fileSize = attributes[FileAttributeKey.size] as? Int64 else { + analytics?.log(message: "File size could not be read") + // none of the logic beyond here will work if we can't get the + // filesize so assume everything is good and hope for the best. + return true + } + fileSizeTotal += fileSize + } catch { + analytics?.log(message: "Could not read file attributes") + } + + if fileSizeTotal >= maxPayloadSize { + analytics?.log(message: "Batch file is too large to be sent") + result = false + } + return result + } + +} diff --git a/Sources/Segment/Plugins/StartupQueue.swift b/Sources/Segment/Plugins/StartupQueue.swift index efb9b107..9ebf157e 100644 --- a/Sources/Segment/Plugins/StartupQueue.swift +++ b/Sources/Segment/Plugins/StartupQueue.swift @@ -45,10 +45,10 @@ internal class StartupQueue: Plugin, Subscriber { extension StartupQueue { internal func runningUpdate(state: System) { + running = state.running if state.running { replayEvents() } - running = state.running } internal func replayEvents() { diff --git a/Sources/Segment/Startup.swift b/Sources/Segment/Startup.swift index 2293e79d..95de8c9c 100644 --- a/Sources/Segment/Startup.swift +++ b/Sources/Segment/Startup.swift @@ -45,10 +45,10 @@ extension Analytics: Subscriber { plugins.append(Context.self) #if os(iOS) || os(tvOS) - plugins += [iOSLifecycleMonitor.self, iOSAppBackground.self, iOSLifecycleEvents.self, DeviceToken.self] + plugins += [iOSLifecycleMonitor.self, iOSLifecycleEvents.self, DeviceToken.self] #endif #if os(watchOS) - plugins.append(watchOSLifecycleMonitor.self) + plugins += [watchOSLifecycleMonitor.self, watchOSLifecycleEvents.self] #endif #if os(macOS) plugins += [macOSLifecycleMonitor.self, DeviceToken.self] diff --git a/Sources/Segment/Utilities/HTTPClient.swift b/Sources/Segment/Utilities/HTTPClient.swift index 10f5b86d..b79a2a11 100644 --- a/Sources/Segment/Utilities/HTTPClient.swift +++ b/Sources/Segment/Utilities/HTTPClient.swift @@ -12,15 +12,15 @@ import FoundationNetworking enum HTTPClientErrors: Error { case badSession + case failedToOpenBatch + case statusCode(code: Int) } public class HTTPClient { private static let defaultAPIHost = "api.segment.io/v1" private static let defaultCDNHost = "cdn-settings.segment.com/v1" - public var sessionDelegate: URLSessionDelegate? - - private var writeKeySessions = [String: URLSession]() + private var session: URLSession private var apiHost: String private var apiKey: String private var cdnHost: String @@ -46,6 +46,8 @@ public class HTTPClient { } else { self.cdnHost = Self.defaultCDNHost } + + self.session = Self.configuredSession(for: self.apiKey) } func segmentURL(for host: String, path: String) -> URL? { @@ -62,45 +64,36 @@ public class HTTPClient { /// - batch: The array of the events, considered a batch of events. /// - completion: The closure executed when done. Passes if the task should be retried or not if failed. @discardableResult - func startBatchUpload(writeKey: String, batch: URL, completion: @escaping (_ succeeded: Bool) -> Void) -> URLSessionDataTask? { + func startBatchUpload(writeKey: String, batch: URL, completion: @escaping (_ result: Result) -> Void) -> URLSessionDataTask? { guard let uploadURL = segmentURL(for: apiHost, path: "/batch") else { - completion(false) + completion(.failure(HTTPClientErrors.failedToOpenBatch)) return nil } var urlRequest = URLRequest(url: uploadURL) urlRequest.httpMethod = "POST" - guard let session = try? configuredSession(for: writeKey) else { - exceptionFailure("Unable to create a HTTPClient session!") - completion(false) - return nil - } - let dataTask = session.uploadTask(with: urlRequest, fromFile: batch) { [weak self] (data, response, error) in if let error = error { self?.analytics.log(message: "Error uploading request \(error.localizedDescription).") - completion(true) - return - } - - if let httpResponse = response as? HTTPURLResponse { + completion(.failure(error)) + } else if let httpResponse = response as? HTTPURLResponse { switch (httpResponse.statusCode) { case 1..<300: - completion(true) + completion(.success(true)) return case 300..<400: self?.analytics.log(message: "Server responded with unexpected HTTP code \(httpResponse.statusCode).") - completion(false) + completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode))) case 429: self?.analytics.log(message: "Server limited client with response code \(httpResponse.statusCode).") - completion(false) + completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode))) case 400..<500: self?.analytics.log(message: "Server rejected payload with HTTP code \(httpResponse.statusCode).") - completion(false) + completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode))) default: // All 500 codes self?.analytics.log(message: "Server rejected payload with HTTP code \(httpResponse.statusCode).") - completion(false) + completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode))) } } } @@ -110,10 +103,6 @@ public class HTTPClient { } func settingsFor(writeKey: String, completion: @escaping (Bool, Settings?) -> Void) { - // Change the key specific to settings so it can be fetched separately - // from write key sessions for uploading. - let settingsKey = "\(writeKey)_settings" - guard let settingsURL = segmentURL(for: cdnHost, path: "/projects/\(writeKey)/settings") else { completion(false, nil) return @@ -122,11 +111,6 @@ public class HTTPClient { var urlRequest = URLRequest(url: settingsURL) urlRequest.httpMethod = "GET" - guard let session = try? configuredSession(for: settingsKey) else { - completion(false, nil) - return - } - let dataTask = session.dataTask(with: urlRequest) { [weak self] (data, response, error) in if let error = error { self?.analytics.log(message: "Error fetching settings \(error.localizedDescription).") @@ -156,9 +140,7 @@ public class HTTPClient { deinit { // finish any tasks that may be processing - for session in writeKeySessions.values { - session.finishTasksAndInvalidate() - } + session.finishTasksAndInvalidate() } } @@ -180,25 +162,16 @@ extension HTTPClient { internal static func getDefaultCDNHost() -> String { return Self.defaultCDNHost } -} - - -extension HTTPClient { - private func configuredSession(for writeKey: String) throws -> URLSession { - if !writeKeySessions.keys.contains(writeKey) { - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = ["Content-Type": "application/json; charset=utf-8", - "Authorization": "Basic \(Self.authorizationHeaderForWriteKey(writeKey))", - "User-Agent": "analytics-ios/\(Analytics.version())"] - let session = URLSession.init(configuration: configuration, delegate: sessionDelegate, delegateQueue: nil) - writeKeySessions[writeKey] = session - } - - guard let session = writeKeySessions[writeKey] else { - throw HTTPClientErrors.badSession - } - + internal static func configuredSession(for writeKey: String) -> URLSession { + let configuration = URLSessionConfiguration.ephemeral + configuration.allowsCellularAccess = true + configuration.timeoutIntervalForResource = 30 + configuration.timeoutIntervalForRequest = 60 + configuration.httpAdditionalHeaders = ["Content-Type": "application/json; charset=utf-8", + "Authorization": "Basic \(Self.authorizationHeaderForWriteKey(writeKey))", + "User-Agent": "analytics-ios/\(Analytics.version())"] + let session = URLSession.init(configuration: configuration, delegate: nil, delegateQueue: nil) return session } }