diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml deleted file mode 100644 index e3a9f51..0000000 --- a/.github/workflows/swift.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Swift - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - cancel_previous: - permissions: write-all - runs-on: ubuntu-latest - steps: - - uses: styfle/cancel-workflow-action@0.9.1 - with: - workflow_id: ${{ github.event.workflow.id }} - - build_and_test_examples: - needs: cancel_previous - runs-on: macos-11 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '13.2.1' - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: /Users/runner/Library/Developer/Xcode/DerivedData - key: ${{ runner.os }}-spm-examples-${{ hashFiles('**/Package.resolved') }} - restore-keys: | - ${{ runner.os }}-spm-examples - - name: build for ios simulator - run: | - cd Example/BasicExample - xcodebuild -project "BasicExample.xcodeproj" -scheme "BasicExample" -sdk iphonesimulator - diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 0000000..721c480 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,9 @@ +version: 1 +builder: + configs: + - platform: ios + documentation_targets: [SegmentBraze] + - platform: ios + scheme: SegmentBrazeUI + - platform: tvos + scheme: SegmentBraze diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bd3cd14 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +## 2.0.0 + +#### Added +- Renames this repository from `analytics-swift-braze` to `braze-segment-swift`. + - This repository is now located at https://github.com/braze-inc/braze-segment-swift. +- Adds the `SegmentBrazeUI` module, which provides the `BrazeDestination` plugin with `BrazeUI` support. + - Use the `SegmentBraze` module if you do not need any Braze-provided UI. +- Adds two optional parameters to the `BrazeDestination` initializer: + - `additionalConfiguration`: When provided, this closure is called with the Braze + configuration object before the SDK initialization. You can use this to set additional + Braze configuration options (e.g. session timeout, push notification automation, etc.). + - `additionalSetup`: When provided, this closure is called with the fully initialized Braze + instance. You can use this to further customize your usage of the Braze SDK (e.g. + register UI delegates, set up messaging subscriptions, etc.) + - See the updated Sample App for an example of how to use these new parameters. +- Adds support for automatically forwarding the [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614151-advertisingidentifier) (_IDFA_) to Braze when making use of the [`IDFACollection`](https://github.com/segmentio/analytics-swift/blob/main/Examples/other_plugins/IDFACollection.swift) Segment plugin. +- Adds support to parse `braze_subscription_groups` in the Identity traits to subscribe and unsubscribe from Braze subscription groups. + +## 1.0.0 + +Initial release. diff --git a/Example/BasicExample/BasicExample.xcodeproj/project.pbxproj b/Example/BasicExample.xcodeproj/project.pbxproj similarity index 59% rename from Example/BasicExample/BasicExample.xcodeproj/project.pbxproj rename to Example/BasicExample.xcodeproj/project.pbxproj index a81bbb3..0fbeacd 100644 --- a/Example/BasicExample/BasicExample.xcodeproj/project.pbxproj +++ b/Example/BasicExample.xcodeproj/project.pbxproj @@ -11,42 +11,19 @@ 46EDC6F827C6B8D100B870D7 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46EDC6F727C6B8D100B870D7 /* ContentView.swift */; }; 46EDC6FA27C6B8D200B870D7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 46EDC6F927C6B8D200B870D7 /* Assets.xcassets */; }; 46EDC6FD27C6B8D200B870D7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 46EDC6FC27C6B8D200B870D7 /* Preview Assets.xcassets */; }; - 46EDC70727C6B8D200B870D7 /* BasicExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46EDC70627C6B8D200B870D7 /* BasicExampleTests.swift */; }; - 46EDC71127C6B8D200B870D7 /* BasicExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46EDC71027C6B8D200B870D7 /* BasicExampleUITests.swift */; }; - 46EDC71327C6B8D200B870D7 /* BasicExampleUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46EDC71227C6B8D200B870D7 /* BasicExampleUITestsLaunchTests.swift */; }; 46EDC72127C6B92C00B870D7 /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = 46EDC72027C6B92C00B870D7 /* Segment */; }; - 7B3C818E2853F14300199D3E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = 7B3C818D2853F14300199D3E /* SegmentBraze */; }; + 662990A72A760B8E003DE911 /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = 662990A62A760B8E003DE911 /* SegmentBraze */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - 46EDC70327C6B8D200B870D7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 46EDC6EA27C6B8D100B870D7 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 46EDC6F127C6B8D100B870D7; - remoteInfo = BasicExample; - }; - 46EDC70D27C6B8D200B870D7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 46EDC6EA27C6B8D100B870D7 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 46EDC6F127C6B8D100B870D7; - remoteInfo = BasicExample; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ 46EDC6F227C6B8D100B870D7 /* BasicExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BasicExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46EDC6F527C6B8D100B870D7 /* BasicExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicExampleApp.swift; sourceTree = ""; }; 46EDC6F727C6B8D100B870D7 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 46EDC6F927C6B8D200B870D7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46EDC6FC27C6B8D200B870D7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 46EDC70227C6B8D200B870D7 /* BasicExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BasicExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 46EDC70627C6B8D200B870D7 /* BasicExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicExampleTests.swift; sourceTree = ""; }; - 46EDC70C27C6B8D200B870D7 /* BasicExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BasicExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 46EDC71027C6B8D200B870D7 /* BasicExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicExampleUITests.swift; sourceTree = ""; }; - 46EDC71227C6B8D200B870D7 /* BasicExampleUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicExampleUITestsLaunchTests.swift; sourceTree = ""; }; - 7BD976A12852789500486D66 /* analytics-swift-braze */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "analytics-swift-braze"; path = ../..; sourceTree = ""; }; + 665FF29B2A7AE8C20011A869 /* BasicExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BasicExample.entitlements; sourceTree = ""; }; + 665FF29C2A7AE8C50011A869 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 66603CB42A75F7DF009B29F2 /* analytics-swift-braze */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "analytics-swift-braze"; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -55,21 +32,7 @@ buildActionMask = 2147483647; files = ( 46EDC72127C6B92C00B870D7 /* Segment in Frameworks */, - 7B3C818E2853F14300199D3E /* SegmentBraze in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 46EDC6FF27C6B8D200B870D7 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 46EDC70927C6B8D200B870D7 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( + 662990A72A760B8E003DE911 /* SegmentBraze in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,8 +44,6 @@ children = ( 7BD976A02852789500486D66 /* Packages */, 46EDC6F427C6B8D100B870D7 /* BasicExample */, - 46EDC70527C6B8D200B870D7 /* BasicExampleTests */, - 46EDC70F27C6B8D200B870D7 /* BasicExampleUITests */, 46EDC6F327C6B8D100B870D7 /* Products */, 7B3C818C2853F14300199D3E /* Frameworks */, ); @@ -92,8 +53,6 @@ isa = PBXGroup; children = ( 46EDC6F227C6B8D100B870D7 /* BasicExample.app */, - 46EDC70227C6B8D200B870D7 /* BasicExampleTests.xctest */, - 46EDC70C27C6B8D200B870D7 /* BasicExampleUITests.xctest */, ); name = Products; sourceTree = ""; @@ -101,6 +60,8 @@ 46EDC6F427C6B8D100B870D7 /* BasicExample */ = { isa = PBXGroup; children = ( + 665FF29C2A7AE8C50011A869 /* Info.plist */, + 665FF29B2A7AE8C20011A869 /* BasicExample.entitlements */, 46EDC6F527C6B8D100B870D7 /* BasicExampleApp.swift */, 46EDC6F727C6B8D100B870D7 /* ContentView.swift */, 46EDC6F927C6B8D200B870D7 /* Assets.xcassets */, @@ -117,23 +78,6 @@ path = "Preview Content"; sourceTree = ""; }; - 46EDC70527C6B8D200B870D7 /* BasicExampleTests */ = { - isa = PBXGroup; - children = ( - 46EDC70627C6B8D200B870D7 /* BasicExampleTests.swift */, - ); - path = BasicExampleTests; - sourceTree = ""; - }; - 46EDC70F27C6B8D200B870D7 /* BasicExampleUITests */ = { - isa = PBXGroup; - children = ( - 46EDC71027C6B8D200B870D7 /* BasicExampleUITests.swift */, - 46EDC71227C6B8D200B870D7 /* BasicExampleUITestsLaunchTests.swift */, - ); - path = BasicExampleUITests; - sourceTree = ""; - }; 7B3C818C2853F14300199D3E /* Frameworks */ = { isa = PBXGroup; children = ( @@ -144,7 +88,7 @@ 7BD976A02852789500486D66 /* Packages */ = { isa = PBXGroup; children = ( - 7BD976A12852789500486D66 /* analytics-swift-braze */, + 66603CB42A75F7DF009B29F2 /* analytics-swift-braze */, ); name = Packages; sourceTree = ""; @@ -167,48 +111,12 @@ name = BasicExample; packageProductDependencies = ( 46EDC72027C6B92C00B870D7 /* Segment */, - 7B3C818D2853F14300199D3E /* SegmentBraze */, + 662990A62A760B8E003DE911 /* SegmentBraze */, ); productName = BasicExample; productReference = 46EDC6F227C6B8D100B870D7 /* BasicExample.app */; productType = "com.apple.product-type.application"; }; - 46EDC70127C6B8D200B870D7 /* BasicExampleTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 46EDC71927C6B8D200B870D7 /* Build configuration list for PBXNativeTarget "BasicExampleTests" */; - buildPhases = ( - 46EDC6FE27C6B8D200B870D7 /* Sources */, - 46EDC6FF27C6B8D200B870D7 /* Frameworks */, - 46EDC70027C6B8D200B870D7 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 46EDC70427C6B8D200B870D7 /* PBXTargetDependency */, - ); - name = BasicExampleTests; - productName = BasicExampleTests; - productReference = 46EDC70227C6B8D200B870D7 /* BasicExampleTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 46EDC70B27C6B8D200B870D7 /* BasicExampleUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 46EDC71C27C6B8D200B870D7 /* Build configuration list for PBXNativeTarget "BasicExampleUITests" */; - buildPhases = ( - 46EDC70827C6B8D200B870D7 /* Sources */, - 46EDC70927C6B8D200B870D7 /* Frameworks */, - 46EDC70A27C6B8D200B870D7 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 46EDC70E27C6B8D200B870D7 /* PBXTargetDependency */, - ); - name = BasicExampleUITests; - productName = BasicExampleUITests; - productReference = 46EDC70C27C6B8D200B870D7 /* BasicExampleUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -222,14 +130,6 @@ 46EDC6F127C6B8D100B870D7 = { CreatedOnToolsVersion = 13.2.1; }; - 46EDC70127C6B8D200B870D7 = { - CreatedOnToolsVersion = 13.2.1; - TestTargetID = 46EDC6F127C6B8D100B870D7; - }; - 46EDC70B27C6B8D200B870D7 = { - CreatedOnToolsVersion = 13.2.1; - TestTargetID = 46EDC6F127C6B8D100B870D7; - }; }; }; buildConfigurationList = 46EDC6ED27C6B8D100B870D7 /* Build configuration list for PBXProject "BasicExample" */; @@ -249,8 +149,6 @@ projectRoot = ""; targets = ( 46EDC6F127C6B8D100B870D7 /* BasicExample */, - 46EDC70127C6B8D200B870D7 /* BasicExampleTests */, - 46EDC70B27C6B8D200B870D7 /* BasicExampleUITests */, ); }; /* End PBXProject section */ @@ -265,20 +163,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 46EDC70027C6B8D200B870D7 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 46EDC70A27C6B8D200B870D7 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -291,38 +175,8 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 46EDC6FE27C6B8D200B870D7 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 46EDC70727C6B8D200B870D7 /* BasicExampleTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 46EDC70827C6B8D200B870D7 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 46EDC71327C6B8D200B870D7 /* BasicExampleUITestsLaunchTests.swift in Sources */, - 46EDC71127C6B8D200B870D7 /* BasicExampleUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - 46EDC70427C6B8D200B870D7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 46EDC6F127C6B8D100B870D7 /* BasicExample */; - targetProxy = 46EDC70327C6B8D200B870D7 /* PBXContainerItemProxy */; - }; - 46EDC70E27C6B8D200B870D7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 46EDC6F127C6B8D100B870D7 /* BasicExample */; - targetProxy = 46EDC70D27C6B8D200B870D7 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin XCBuildConfiguration section */ 46EDC71427C6B8D200B870D7 /* Debug */ = { isa = XCBuildConfiguration; @@ -445,11 +299,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = BasicExample/BasicExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"BasicExample/Preview Content\""; + DEVELOPMENT_TEAM = 5GLZKGNWQ3; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BasicExample/Info.plist; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -473,11 +330,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = BasicExample/BasicExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"BasicExample/Preview Content\""; + DEVELOPMENT_TEAM = 5GLZKGNWQ3; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BasicExample/Info.plist; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -496,78 +356,6 @@ }; name = Release; }; - 46EDC71A27C6B8D200B870D7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.segment.BasicExampleTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BasicExample.app/BasicExample"; - }; - name = Debug; - }; - 46EDC71B27C6B8D200B870D7 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.segment.BasicExampleTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BasicExample.app/BasicExample"; - }; - name = Release; - }; - 46EDC71D27C6B8D200B870D7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.segment.BasicExampleUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = BasicExample; - }; - name = Debug; - }; - 46EDC71E27C6B8D200B870D7 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.segment.BasicExampleUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = BasicExample; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -589,24 +377,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 46EDC71927C6B8D200B870D7 /* Build configuration list for PBXNativeTarget "BasicExampleTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 46EDC71A27C6B8D200B870D7 /* Debug */, - 46EDC71B27C6B8D200B870D7 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 46EDC71C27C6B8D200B870D7 /* Build configuration list for PBXNativeTarget "BasicExampleUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 46EDC71D27C6B8D200B870D7 /* Debug */, - 46EDC71E27C6B8D200B870D7 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -626,7 +396,7 @@ package = 46EDC71F27C6B92C00B870D7 /* XCRemoteSwiftPackageReference "analytics-swift" */; productName = Segment; }; - 7B3C818D2853F14300199D3E /* SegmentBraze */ = { + 662990A62A760B8E003DE911 /* SegmentBraze */ = { isa = XCSwiftPackageProductDependency; productName = SegmentBraze; }; diff --git a/Example/BasicExample/BasicExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/BasicExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Example/BasicExample/BasicExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Example/BasicExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Example/BasicExample/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Example/BasicExample/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Example/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Example/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..26b8aa8 --- /dev/null +++ b/Example/BasicExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,41 @@ +{ + "pins" : [ + { + "identity" : "analytics-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/analytics-swift", + "state" : { + "branch" : "main", + "revision" : "c8fd5fdf59299f00b3e4303a1b12a6d88893bf56" + } + }, + { + "identity" : "braze-swift-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/braze-inc/braze-swift-sdk", + "state" : { + "revision" : "80d27557b74de80d4e62a285e63b72138fad8be6", + "version" : "6.6.0" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "633996a807442ec28df9d33b0f88ce57a0e2fdbf", + "version" : "5.17.0" + } + }, + { + "identity" : "sovran-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/Sovran-Swift.git", + "state" : { + "revision" : "64f3b5150c282a34af4578188dce2fd597e600e3", + "version" : "1.1.0" + } + } + ], + "version" : 2 +} diff --git a/Example/BasicExample/BasicExample/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/BasicExample/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Example/BasicExample/BasicExample/Assets.xcassets/AccentColor.colorset/Contents.json rename to Example/BasicExample/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Example/BasicExample/BasicExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/BasicExample/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Example/BasicExample/BasicExample/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Example/BasicExample/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Example/BasicExample/BasicExample/Assets.xcassets/Contents.json b/Example/BasicExample/Assets.xcassets/Contents.json similarity index 100% rename from Example/BasicExample/BasicExample/Assets.xcassets/Contents.json rename to Example/BasicExample/Assets.xcassets/Contents.json diff --git a/Example/BasicExample/BasicExample.entitlements b/Example/BasicExample/BasicExample.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/Example/BasicExample/BasicExample.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/Example/BasicExample/BasicExample/BasicExampleApp.swift b/Example/BasicExample/BasicExample/BasicExampleApp.swift deleted file mode 100644 index 50443a9..0000000 --- a/Example/BasicExample/BasicExample/BasicExampleApp.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// BasicExampleApp.swift -// BasicExample -// -// Created by Brandon Sneed on 2/23/22. -// - -import SwiftUI -import Segment -import SegmentBraze - -@main -struct BasicExampleApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} - -extension Analytics { - static var main: Analytics { - let analytics = Analytics(configuration: Configuration(writeKey: "") - .flushAt(3) - .trackApplicationLifecycleEvents(true)) - analytics.add(plugin: BrazeDestination()) - return analytics - } -} diff --git a/Example/BasicExample/BasicExample/ContentView.swift b/Example/BasicExample/BasicExample/ContentView.swift deleted file mode 100644 index 92f09f7..0000000 --- a/Example/BasicExample/BasicExample/ContentView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// ContentView.swift -// BasicExample -// -// Created by Brandon Sneed on 2/23/22. -// - -import SwiftUI -import Segment - -struct ContentView: View { - var body: some View { - VStack { - HStack { - Button(action: { - var traits = [String:Any]() - var products = [Any]() - products.append(["price":1,"quantity":1,"productId":"foo","color":"blue", "dupe":"override"]) - products.append(["price":2,"quantity":2,"productId":"bar","size":"large"]) - products.append(["price":3,"quantity":3,"productId":"baz","fit":9]) - traits["products"] = products - traits["dupe"] = "default" - traits["general"] = "value" - Analytics.main.track(name: "Order Completed", properties: traits) - }, label: { - Text("Track") - }).padding(6) - Button(action: { - Analytics.main.screen(title: "Screen appeared") - }, label: { - Text("Screen") - }).padding(6) - }.padding(8) - HStack { - Button(action: { - Analytics.main.group(groupId: "12345-Group") - Analytics.main.log(message: "Started group") - }, label: { - Text("Group") - }).padding(6) - Button(action: { - var traits = [String:Any]() - traits["birthday"] = "1980-06-07T01:21:13Z" - traits["email"] = "testuser@test.com" - traits["firstName"] = "fnu" - traits["lastName"] = "lnu" - traits["gender"] = "male" - traits["phone"] = "1-234-5678" - traits["address"] = ["city": "Paris", "country": "USA"] - traits["foo"] = ["bar": "baz"] - Analytics.main.identify(userId: "X-1234567890", traits: traits) - }, label: { - Text("Identify") - }).padding(6) - }.padding(8) - }.onAppear { - Analytics.main.track(name: "onAppear") - print("Executed Analytics onAppear()") - }.onDisappear { - Analytics.main.track(name: "onDisappear") - print("Executed Analytics onDisappear()") - } - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/Example/BasicExample/BasicExampleApp.swift b/Example/BasicExample/BasicExampleApp.swift new file mode 100644 index 0000000..fce4a08 --- /dev/null +++ b/Example/BasicExample/BasicExampleApp.swift @@ -0,0 +1,51 @@ +import Segment +import SegmentBraze +import SwiftUI + +let segmentWriteKey = "6986CcMHxN4rXpYe3ieKBTtXQHryZVRi" + +@main +struct BasicExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} + +extension Analytics { + static var main: Analytics = { + let analytics = Analytics( + configuration: Configuration(writeKey: segmentWriteKey) + .trackApplicationLifecycleEvents(true) + .flushAt(3) + .flushInterval(10) + ) + analytics.add(plugin: Analytics.makeBrazeDestination()) + return analytics + }() + + static func makeBrazeDestination() -> BrazeDestination { + BrazeDestination( + additionalConfiguration: { configuration in + // Configure the Braze SDK here, e.g.: + // - Log general SDK information and errors + configuration.logger.level = .info + + // - Enable automatic push notifications support + configuration.push.automation = true + configuration.push.automation.requestAuthorizationAtLaunch = false + + // - Enable universal link forwarding + configuration.forwardUniversalLinks = true + + // - Set the trigger minimum time interval + configuration.triggerMinimumTimeInterval = 35 + }, + additionalSetup: { braze in + // Post initialization setup here (e.g. setting up delegates, subscriptions, keep a + // reference to the initialized Braze instance, etc.) + } + ) + } +} diff --git a/Example/BasicExample/BasicExampleTests/BasicExampleTests.swift b/Example/BasicExample/BasicExampleTests/BasicExampleTests.swift deleted file mode 100644 index 7ca22a2..0000000 --- a/Example/BasicExample/BasicExampleTests/BasicExampleTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// BasicExampleTests.swift -// BasicExampleTests -// -// Created by Brandon Sneed on 2/23/22. -// - -import XCTest -@testable import BasicExample - -class BasicExampleTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - 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. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Example/BasicExample/BasicExampleUITests/BasicExampleUITests.swift b/Example/BasicExample/BasicExampleUITests/BasicExampleUITests.swift deleted file mode 100644 index f065fbd..0000000 --- a/Example/BasicExample/BasicExampleUITests/BasicExampleUITests.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// BasicExampleUITests.swift -// BasicExampleUITests -// -// Created by Brandon Sneed on 2/23/22. -// - -import XCTest - -class BasicExampleUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/Example/BasicExample/BasicExampleUITests/BasicExampleUITestsLaunchTests.swift b/Example/BasicExample/BasicExampleUITests/BasicExampleUITestsLaunchTests.swift deleted file mode 100644 index 86fad66..0000000 --- a/Example/BasicExample/BasicExampleUITests/BasicExampleUITestsLaunchTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// BasicExampleUITestsLaunchTests.swift -// BasicExampleUITests -// -// Created by Brandon Sneed on 2/23/22. -// - -import XCTest - -class BasicExampleUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} diff --git a/Example/BasicExample/ContentView.swift b/Example/BasicExample/ContentView.swift new file mode 100644 index 0000000..f12a595 --- /dev/null +++ b/Example/BasicExample/ContentView.swift @@ -0,0 +1,80 @@ +import Segment +import SwiftUI + +struct ContentView: View { + var body: some View { + List { + Section("Segment Actions") { + Button("Track Purchase") { + var traits = [String: Any]() + var products = [Any]() + products.append([ + "price": 1, + "quantity": 1, + "productId": "foo", + "color": "blue", + "dupe": "override", + ] as [String : Any]) + products.append([ + "price": 2, + "quantity": 2, + "productId": "bar", + "size": "large" + ] as [String : Any]) + products.append([ + "price": 3, + "quantity": 3, + "productId": "baz", + "fit": 9 + ] as [String : Any]) + traits["products"] = products + traits["dupe"] = "default" + traits["general"] = "value" + traits["revenue"] = 14.0 + Analytics.main.track(name: "Order Completed", properties: traits) + } + Button("Track Custom Event") { + let properties: [String: Any] = [ + "foo": "baz", + "count": 15, + "correct": false + ] + Analytics.main.track(name: "braze-custom-event", properties: properties) + } + Button("Screen") { + Analytics.main.screen(title: "Screen appeared") + } + Button("Group") { + Analytics.main.group(groupId: "12345-Group") + Analytics.main.log(message: "Started group") + } + Button("Identify") { + var traits = [String: Any]() + traits["birthday"] = "1980-06-07T01:21:13Z" + traits["email"] = "testuser@test.com" + traits["firstName"] = "fnu" + traits["lastName"] = "lnu" + traits["gender"] = "male" + traits["phone"] = "1-234-5678" + traits["address"] = ["city": "Paris", "country": "USA"] + traits["foo"] = ["bar": "baz"] + Analytics.main.identify(userId: "X-1234567890", traits: traits) + } + } + } + .onAppear { + Analytics.main.track(name: "onAppear") + print("Executed Analytics onAppear()") + } + .onDisappear { + Analytics.main.track(name: "onDisappear") + print("Executed Analytics onDisappear()") + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Example/BasicExample/Info.plist b/Example/BasicExample/Info.plist new file mode 100644 index 0000000..ca9a074 --- /dev/null +++ b/Example/BasicExample/Info.plist @@ -0,0 +1,10 @@ + + + + + UIBackgroundModes + + remote-notification + + + diff --git a/Example/BasicExample/BasicExample/Preview Content/Preview Assets.xcassets/Contents.json b/Example/BasicExample/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from Example/BasicExample/BasicExample/Preview Content/Preview Assets.xcassets/Contents.json rename to Example/BasicExample/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/LICENSE b/LICENSE index 29e1ab6..da56d94 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,10 @@ -MIT License +Copyright (c) 2023 Braze, Inc. +All rights reserved. -Copyright (c) 2021 Segment +* The use of source code or binaries contained within Braze's sample apps, documentation, stub APIs and other related utilities is permitted only to enable testing and quality assurance of integrations with the Braze platform by current customers of Braze. +* The use of source code or binaries contained within Braze's SDKs is permitted only to enable use of the Braze platform by current customers of Braze. +* Modification of source code contained within this repository or inclusion of such source in any non-Braze app, website, or related code is only permitted provided that all other conditions set forth herein are met. +* Neither the name "Braze" nor the names of any of its contributors may be used to endorse or promote products derived from this software without the express prior written permission of Braze. +* Redistribution of source code or binaries is strictly prohibited except with the express prior written permission of Braze. Any such redistribution must retain the above copyright notice, this list of conditions and the following disclaimer: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF THE USER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Package.swift b/Package.swift index 7d16d08..3e81422 100644 --- a/Package.swift +++ b/Package.swift @@ -1,43 +1,48 @@ -// swift-tools-version:5.3 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version:5.7 import PackageDescription let package = Package( - name: "SegmentBraze", - platforms: [ - .macOS("10.15"), - .iOS("13.0"), - .tvOS("11.0"), - .watchOS("7.1") - ], - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "SegmentBraze", - targets: ["SegmentBraze"]), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - .package( - name: "Segment", - url: "https://github.com/segmentio/analytics-swift.git", - from: "1.1.2" - ), - .package( - name: "braze-swift-sdk", - url:"https://github.com/braze-inc/braze-swift-sdk.git", - from: "5.0.0"), - ], - 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: "SegmentBraze", - dependencies: ["Segment", .product(name: "BrazeKit", package: "braze-swift-sdk")]), - - // TESTS ARE HANDLED VIA THE EXAMPLE APP. - ] + name: "braze-segment-swift", + platforms: [ + .iOS("13.0"), + .tvOS("11.0"), + ], + products: [ + .library( + name: "SegmentBraze", + targets: ["SegmentBraze"] + ), + .library( + name: "SegmentBrazeUI", + targets: ["SegmentBrazeUI"] + ), + ], + dependencies: [ + .package( + url: "https://github.com/segmentio/analytics-swift", + from: "1.1.2" + ), + .package( + url:"https://github.com/braze-inc/braze-swift-sdk", + from: "6.6.0" + ), + ], + targets: [ + .target( + name: "SegmentBraze", + dependencies: [ + .product(name: "Segment", package: "analytics-swift"), + .product(name: "BrazeKit", package: "braze-swift-sdk"), + ] + ), + .target( + name: "SegmentBrazeUI", + dependencies: [ + .product(name: "Segment", package: "analytics-swift"), + .product(name: "BrazeKit", package: "braze-swift-sdk"), + .product(name: "BrazeUI", package: "braze-swift-sdk"), + ] + ), + ] ) - diff --git a/README.md b/README.md index f273eff..a08a379 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,96 @@ -# Analytics-Swift Braze +# Braze-Segment-Swift -Add Braze purchase event tracking support to your applications via this plugin for [Analytics-Swift](https://github.com/segmentio/analytics-swift) +Add this plugin to your applications to support both the [Segment `Analytics-Swift` SDK](https://github.com/segmentio/analytics-swift) and the [Braze Swift SDK](https://github.com/braze-inc/braze-swift-sdk/). ## Adding the dependency ### via Xcode In the Xcode `File` menu, click `Add Packages`. You'll see a dialog where you can search for Swift packages. In the search field, enter the URL to this repo. -https://github.com/segment-integrations/analytics-swift-braze +``` +https://github.com/braze-inc/braze-segment-swift +``` -You'll then have the option to pin to a version, or specific branch, as well as which project in your workspace to add it to. Once you've made your selections, click the `Add Package` button. +You'll then have the option to pin to a version, or specific branch, as well as select which project in your workspace to add it to. Once you've made your selections, click the `Add Package` button. ### via Package.swift -Open your Package.swift file and add the following do your the `dependencies` section: +Open your `Package.swift` file and add the following do your the `dependencies` section: -``` +```swift .package( - name: "Segment", - url: "https://github.com/segment-integrations/analytics-swift-braze.git", - from: "1.0.0" - ), + url: "https://github.com/braze-inc/braze-segment-swift", + from: "2.0.0" +), ``` +Update your target dependencies to include either `SegmentBraze` or `SegmentBrazeUI`: + +```swift +.target( + name: "...", + dependencies: [ + .product(name: "Segment", package: "analytics-swift"), + .product(name: "SegmentBraze", package: "braze-segment-swift"), + ] +), +``` + +> Note: `SegmentBraze` does not provide any UI components and does not depend on `BrazeUI`. If you need UI components, use `SegmentBrazeUI` in place of `SegmentBraze` – but do not import both of them. ## Using the Plugin in your App -Open the file where you setup and configure the Analytics-Swift library. Add this plugin to the list of imports. +Open the file where you setup and configure the Analytics-Swift library. Add this plugin to the list of imports. -``` +```swift import Segment -import SegmentBraze // <-- Add this line +import SegmentBraze // <-- Add this line, or replace with `import SegmentBrazeUI` if you need UI components ``` Just under your Analytics-Swift library setup, call `analytics.add(plugin: ...)` to add an instance of the plugin to the Analytics timeline. -``` +```swift let analytics = Analytics(configuration: Configuration(writeKey: "") .flushAt(3) .trackApplicationLifecycleEvents(true)) analytics.add(plugin: BrazeDestination()) ``` -Your events will now be given Braze session data and start flowing to Braze via Cloud Mode. - +Your events will now be given Braze session data and start flowing to Braze. -## Support +### Additional Configuration -Please use Github issues, Pull Requests, or feel free to reach out to our [support team](https://segment.com/help/). +The `BrazeDestination` initializer accepts two optional parameters allowing you more control over the SDK's behavior. For a full list of available configurations, refer to [`Braze.Configuration`](https://braze-inc.github.io/braze-swift-sdk/documentation/brazekit/braze/configuration-swift.class). -## Integrating with Segment +```swift +BrazeDestination( + additionalConfiguration: { configuration in + // Configure the Braze SDK here, e.g.: + // - Debug / verbose logs + configuration.logger.level = .debug -Interested in integrating your service with us? Check out our [Partners page](https://segment.com/partners/) for more details. + // - Enable automatic push notifications support + configuration.push.automation = true + configuration.push.automation.requestAuthorizationAtLaunch = false -## License -``` -MIT License - -Copyright (c) 2021 Segment - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + // - Enable universal link forwarding + configuration.forwardUniversalLinks = true + }, + additionalSetup: { braze in + // Post initialization setup here (e.g. setting up delegates, subscriptions, keep a + // reference to the initialized Braze instance, etc.) + } +) ``` -***Third Party Notice*** +### Push Notifications Support + +To enable push notifications support, refer to the [_Push Notifications_](https://www.braze.com/docs/developer_guide/platform_integration_guides/swift/push_notifications/) documentation. To keep the integration minimal, the Braze SDK provides push automation features (see sample code above and the [`automation`](https://braze-inc.github.io/braze-swift-sdk/documentation/brazekit/braze/configuration-swift.class/push-swift.class/automation-swift.property) documentation). + +### IDFA Collection + +When making use of the [`IDFACollection`](https://github.com/segmentio/analytics-swift/blob/main/Examples/other_plugins/IDFACollection.swift) Segment plugin, the `BrazeDestination` will automatically forward the collected IDFA to Braze. -Use of source code or binaries contained within Braze’s SDKs is permitted only to enable use of the Braze platform by customers of Braze. You must follow the terms of the below license found in the braze-swift-sdk. +## Questions? -[Braze License Agreement](https://github.com/braze-inc/braze-swift-sdk/blob/main/Sources/BrazeKitResources/Resources/braze.license) +If you have questions, please contact [support@braze.com](mailto:support@braze.com) or open a [GitHub Issue](https://github.com/braze-inc/braze-segment-swift/issues). diff --git a/Sources/SegmentBraze/BrazeDestination.swift b/Sources/SegmentBraze/BrazeDestination.swift index e33fbc5..45e109c 100644 --- a/Sources/SegmentBraze/BrazeDestination.swift +++ b/Sources/SegmentBraze/BrazeDestination.swift @@ -1,286 +1,435 @@ -// -// BrazeDestination.swift -// BrazeDestination -// -// Created by Michael Grosse Huelsewiesche on 5/17/22. -// - -// NOTE: You can see this plugin in use in the BasicExample application. -// - -// MIT License -// -// Copyright (c) 2022 Segment -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - +import BrazeKit import Foundation import Segment -import BrazeKit -import UIKit - -/** - An implementation of the Braze Analytics device mode destination as a plugin. - */ - -public class BrazeDestination: DestinationPlugin { - public let timeline = Timeline() - public let type = PluginType.destination - public let key = "Appboy" - public var analytics: Analytics? = nil - var braze: Braze? = nil - - private var brazeSettings: BrazeSettings? - - public init() { } - - public func update(settings: Settings, type: UpdateType) { - // Skip if you have a singleton and don't want to keep updating via settings. - guard type == .initial else { return } - - // Grab the settings and assign them for potential later usage. - // Note: Since integrationSettings is generic, strongly type the variable. - guard let tempSettings: BrazeSettings = settings.integrationSettings(forPlugin: self) else { return } - brazeSettings = tempSettings - - var configuration = Braze.Configuration( - apiKey: brazeSettings?.apiKey ?? "", - endpoint: brazeSettings?.customEndpoint ?? "" - ) - configuration.api.addSdkMetadata([Braze.Configuration.Api.SdkMetadata.segment]) - - braze = Braze(configuration: configuration) + +#if canImport(BrazeUI) + import BrazeUI +#endif + +// MARK: - BrazeDestination + +/// The Braze destination plugin for the Segment SDK. +/// +/// The Braze destination can be used like any other Segment destination and will inherit the +/// settings from the Segment dashboard: +/// ``` +/// analytics.add(plugin: BrazeDestination()) +/// ``` +/// +/// To customize the Braze SDK further, you can use the `additionalConfiguration` closure: +/// ``` +/// let brazeDestination = BrazeDestination( +/// additionalConfiguration: { configuration in +/// // Enable debug / verbose logs +/// configuration.logger.level = .debug +/// +/// // Enable push support (disabling automatic push authorization prompt) +/// configuration.push.automation = true +/// configuration.push.automation.requestAuthorizationAtLaunch = false +/// } +/// ) +/// analytics.add(plugin: BrazeDestination()) +/// ``` +/// +/// An `additionalSetup` closure is also available to customize the Braze SDK further after it has +/// been initialized: +/// ``` +/// let brazeDestination = BrazeDestination( +/// additionalSetup: { braze in +/// // Save the Braze instance on the AppDelegate for later use. +/// AppDelegate.braze = braze +/// } +/// ) +/// analytics.add(plugin: BrazeDestination()) +/// ``` +/// +/// See the Braze Swift SDK [documentation][1] for more information. +/// +/// [1]: https://braze-inc.github.io/braze-swift-sdk +public class BrazeDestination: DestinationPlugin, VersionedPlugin { + + // MARK: - Properties + + // - DestinationPlugin + + public let timeline = Timeline() + public let type = PluginType.destination + public let key = "Appboy" + public weak var analytics: Analytics? = nil + + // - Braze + + /// The Braze instance. + public internal(set) var braze: Braze? = nil + + #if canImport(BrazeUI) + /// The Braze in-app message UI, available when `automaticInAppMessageRegistrationEnabled` is + /// set to `true` on the Segment dashboard. + public internal(set) var inAppMessageUI: BrazeInAppMessageUI? = nil + #endif + + private let additionalConfiguration: ((Braze.Configuration) -> Void)? + private let additionalSetup: ((Braze) -> Void)? + + // - Braze / Segment bridge + + private var logPurchaseWhenRevenuePresent: Bool = true + + // MARK: - Initialization + + /// Creates and returns a Braze destination plugin for the Segment SDK. + /// + /// See ``BrazeDestination`` for more information. + /// + /// - Parameters: + /// - additionalConfiguration: When provided, this closure is called with the Braze + /// configuration object before the SDK initialization. You can use this to set additional + /// Braze configuration options (e.g. session timeout, push notification automation, etc.). + /// - additionalSetup: When provided, this closure is called with the fully initialized Braze + /// instance. You can use this to customize further your usage of the Braze SDK (e.g. + /// register UI delegates, messaging subscriptions, etc.) + public init( + additionalConfiguration: ((Braze.Configuration) -> Void)? = nil, + additionalSetup: ((Braze) -> Void)? = nil + ) { + self.additionalConfiguration = additionalConfiguration + self.additionalSetup = additionalSetup + } + + // MARK: - Plugin + + public func update(settings: Settings, type: UpdateType) { + guard + type == .initial, + let brazeSettings: BrazeSettings = settings.integrationSettings(forPlugin: self), + let configuration = makeBrazeConfiguration(from: brazeSettings) + else { + log( + message: + """ + Invalid settings, BrazeDestination will not be initialized: + - settings: \(settings.prettyPrint()) + """ + ) + return } - - public func identify(event: IdentifyEvent) -> IdentifyEvent? { - - if let userId = event.userId, !userId.isEmpty { - braze?.changeUser(userId: userId) - } + self.log(message: "Braze Destination is enabled") + braze = makeBraze(from: brazeSettings, configuration: configuration) + logPurchaseWhenRevenuePresent = brazeSettings.logPurchaseWhenRevenuePresent ?? true + } - if let traits = event.traits?.dictionaryValue { - if let birthday = traits["birthday"] as? String { - let dateformatter = DateFormatter() - dateformatter.locale = Locale(identifier: "en_US_POSIX") - dateformatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" // RFC3339 format date - let formattedBirthday = dateformatter.date(from: birthday) - braze?.user.set(dateOfBirth: formattedBirthday) - } - - if let email = traits["email"] as? String { - braze?.user.set(email: email) - } - - if let firstName = traits["firstName"] as? String { - braze?.user.set(firstName: firstName) - } - - if let lastName = traits["lastName"] as? String { - braze?.user.set(lastName: lastName) - } - - if let gender = traits["gender"] as? String { - if gender.lowercased() == "m" || gender.lowercased() == "male" { - braze?.user.set(gender: Braze.User.Gender.male) - } - else if gender.lowercased() == "f" || gender.lowercased() == "female" { - braze?.user.set(gender: Braze.User.Gender.female) - } - else if gender.lowercased() == "na" || gender.lowercased() == "not applicable" { - braze?.user.set(gender: Braze.User.Gender.notApplicable) - } - else if gender.lowercased() == "other" { - braze?.user.set(gender: Braze.User.Gender.other) - } - else if gender.lowercased() == "prefer not to say" { - braze?.user.set(gender: Braze.User.Gender.preferNotToSay) - } - else { - braze?.user.set(gender: Braze.User.Gender.unknown) - } - } - - if let phone = traits["phone"] as? String { - braze?.user.set(phoneNumber: phone) - } - - if let address = traits["address"] as? Dictionary { - if let city = address["city"] as? String { - braze?.user.set(homeCity: city) - } - if let country = address["country"] as? String { - braze?.user.set(country: country) - } - } - - let brazeTraits = ["birthday", "email", "firstName", "lastName", "gender", "phone", "address", "anonymousID"] - - for trait in traits where !brazeTraits.contains(trait.key) { - switch trait.value { - case let val as String: - braze?.user.setCustomAttribute(key: trait.key, value: val) - case let val as Date: - braze?.user.setCustomAttribute(key: trait.key, value: val) - case let val as Bool: - braze?.user.setCustomAttribute(key: trait.key, value: val) - case let val as Int: - braze?.user.setCustomAttribute(key: trait.key, value: val) - case let val as Double: - braze?.user.setCustomAttribute(key: trait.key, value: val) - case let val as Array: - braze?.user.setCustomAttributeArray(key: trait.key, array: val) - default: - braze?.user.setCustomAttribute(key: trait.key, value: String(describing: trait.value)) - } - } - } + public func execute(event: T?) -> T? where T: RawEvent { + // Intercept ATT / IDFA events to forward them to Braze + if let context = event?.context?.dictionaryValue { + if let adTrackingEnabled = context[keyPath: "device.adTrackingEnabled"] as? Bool { + braze?.set(adTrackingEnabled: adTrackingEnabled) + } + if let idfa = context[keyPath: "device.advertisingId"] as? String { + braze?.set(identifierForAdvertiser: idfa) + } + } - return event + // Default implementation (Segment/Timeline.swift) + var result: T? = event + switch result { + case let r as IdentifyEvent: + result = self.identify(event: r) as? T + case let r as TrackEvent: + result = self.track(event: r) as? T + case let r as ScreenEvent: + result = self.screen(event: r) as? T + case let r as AliasEvent: + result = self.alias(event: r) as? T + case let r as GroupEvent: + result = self.group(event: r) as? T + default: + break } - - public func track(event: TrackEvent) -> TrackEvent? { - let properties = event.properties?.dictionaryValue - let revenue = self.extractRevenue(key: "revenue", from: properties) - if (revenue != nil && revenue != 0) || event.event == "Order Completed" || event.event == "Completed Order" { - let currency = properties?["currency"] as? String ?? "USD" - - if properties != nil { - var appboyProperties = properties! - appboyProperties["currency"] = nil - appboyProperties["revenue"] = nil - if let products = appboyProperties["products"] as? Array { - appboyProperties["products"] = nil - for product in products { - var productDict = product as? Dictionary - let productId = productDict?["productId"] as? String ?? "Unknown" - let productRevenue = self.extractRevenue(key: "price", from: productDict) - let productQuantity = productDict?["quantity"] as? Int - productDict?["productId"] = nil - productDict?["price"] = nil - productDict?["quantity"] = nil - var productProperties = appboyProperties - if let productDict = productDict { - productProperties.merge(productDict, uniquingKeysWith: { (_, new) in new } ) - } - braze?.logPurchase(productId: productId, - currency: currency, - price: productRevenue ?? 0, - quantity: productQuantity ?? 0, - properties: productProperties) - } - } else { - braze?.logPurchase(productId: event.event, - currency: currency, - price: revenue ?? 0, - quantity: 1, - properties: appboyProperties) - } - } else { - braze?.logPurchase(productId: event.event, - currency: currency, - price: revenue ?? 0, - quantity: 1) - } - } - return event + return result + } + + // MARK: - EventPlugin + + public func identify(event: IdentifyEvent) -> IdentifyEvent? { + + guard let braze else { return event } + + if let userId = event.userId, !userId.isEmpty { + braze.changeUser(userId: userId) } -} -extension BrazeDestination: VersionedPlugin { - public static func version() -> String { - return __destination_version + guard let traits = event.traits?.dictionaryValue else { return event } + + // Defined / known user attributes + if let birthday = traits["birthday"] as? String { + let dateformatter = DateFormatter() + dateformatter.locale = Locale(identifier: "en_US_POSIX") + dateformatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" // RFC3339 format date + let formattedBirthday = dateformatter.date(from: birthday) + braze.user.set(dateOfBirth: formattedBirthday) } -} -private struct BrazeSettings: Codable { - let sessionTimeoutInSeconds: Double? - let doNotLoadFontAwesome: Bool? - let safariWebsitePushId: String? - let enableLogging: Bool? - let restCustomEndpoint: String? - let version: String? - let type: String? - let onlyTrackKnownUsersOnWeb: Bool? - let openInAppMessagesInNewTab: Bool? - let trackAllPages: Bool? - let trackNamedPages: Bool? - let apiKey: String - let customEndpoint: String? - let logPurchaseWhenRevenuePresent: Bool? - let automaticallyDisplayMessages: Bool? - let updateExistingOnly: Bool? - let automatic_in_app_message_registration_enabled: Bool? - let localization: String? - let openNewsFeedCardsInNewTab: Bool? - let datacenter: String - let minimumIntervalBetweenTriggerActionsInSeconds: Double? - let allowCrawlerActivity: Bool? - let enableHtmlInAppMessages: Bool? -// let versionSettings: Dictionary // not Codable - let bundlingStatus: String? - let serviceWorkerLocation: String? - let requireExplicitInAppMessageDismissal: Bool? - - -} + if let email = traits["email"] as? String { + braze.user.set(email: email) + } -extension BrazeDestination { - internal func extractRevenue(key: String, from properties: [String: Any]?) -> Double? { - - if let revenueDouble = properties?[key] as? Double { - return revenueDouble - } - - if let revenueString = properties?[key] as? String { - let revenueDouble = Double(revenueString) - return revenueDouble - } - - return nil + if let firstName = traits["firstName"] as? String { + braze.user.set(firstName: firstName) } - - - internal func extractCurrency(key: String, from properties: [String: Any]?, withDefault value: String? = nil) -> String? { - - if let currency = properties?[key] as? String { - return currency - } - - return "USD" + + if let lastName = traits["lastName"] as? String { + braze.user.set(lastName: lastName) } - -} - -// Rules for converting keys and values to the proper formats that bridge -// from Segment to the Partner SDK. These are only examples. -private extension BrazeDestination { - - static var eventNameMap = ["ADD_TO_CART": "Product Added", - "PRODUCT_TAPPED": "Product Tapped"] - - static var eventValueConversion: ((_ key: String, _ value: Any) -> Any) = { (key, value) in - if let valueString = value as? String { - return valueString - .replacingOccurrences(of: "-", with: "_") - .replacingOccurrences(of: " ", with: "_") - } else { - return value + + if let gender = (traits["gender"] as? String)?.lowercased() { + if Keys.maleTokens.contains(gender) { + braze.user.set(gender: .male) + } else if Keys.femaleTokens.contains(gender) { + braze.user.set(gender: .female) + } else if Keys.notApplicableTokens.contains(gender) { + braze.user.set(gender: .notApplicable) + } else if gender.lowercased() == "other" { + braze.user.set(gender: .other) + } else if gender.lowercased() == "prefer not to say" { + braze.user.set(gender: .preferNotToSay) + } else { + braze.user.set(gender: .unknown) + } + } + + if let phone = traits["phone"] as? String { + braze.user.set(phoneNumber: phone) + } + + if let address = traits["address"] as? [String: Any] { + if let city = address["city"] as? String { + braze.user.set(homeCity: city) + } + if let country = address["country"] as? String { + braze.user.set(country: country) + } + } + + // Subscription groups + if let subscriptions = traits[Keys.subscriptionGroup.rawValue] as? [[String: Any]] { + for subscription in subscriptions { + guard + let groupID = subscription[Keys.subscriptionId.rawValue] as? String, + let groupState = subscription[Keys.subscriptionState.rawValue] as? String + else { continue } + switch groupState { + case "subscribed": + braze.user.addToSubscriptionGroup(id: groupID) + case "unsubscribed": + braze.user.removeFromSubscriptionGroup(id: groupID) + default: + log(message: "Unsupported subscription state '\(groupState)' for group '\(groupID)'") } + } + } + + // Custom user attributes + for trait in traits where !Keys.reservedKeys.contains(trait.key) { + let key = trait.key + switch trait.value { + case let value as String: + braze.user.setCustomAttribute(key: key, value: value) + case let value as Date: + braze.user.setCustomAttribute(key: key, value: value) + case let value as Bool: + braze.user.setCustomAttribute(key: key, value: value) + case let value as Int: + braze.user.setCustomAttribute(key: key, value: value) + case let value as Double: + braze.user.setCustomAttribute(key: key, value: value) + case let value as [String]: + braze.user.setCustomAttribute(key: key, array: value) + default: + braze.user.setCustomAttribute(key: key, value: String(describing: trait.value)) + } } + + return event + } + + public func track(event: TrackEvent) -> TrackEvent? { + let properties = event.properties + let revenue = extractRevenue(key: "revenue", from: properties?.dictionaryValue) + let treatAsPurchase = revenue != nil && logPurchaseWhenRevenuePresent + + switch event.event { + case Keys.installEventName.rawValue: + setAttributionData(properties: properties) + case Keys.purchaseEventName1.rawValue where treatAsPurchase, + Keys.purchaseEventName2.rawValue where treatAsPurchase: + logPurchase(name: event.event, properties: event.properties?.dictionaryValue ?? [:]) + default: + logCustomEvent(name: event.event, properties: event.properties?.dictionaryValue) + } + + return event + } + + public func reset() { + self.log(message: "Wiping data and resetting Braze.") + braze?.wipeData() + braze?.enabled = true + } + + public func flush() { + self.log(message: "Calling braze.requestImmediateDataFlush().") + braze?.requestImmediateDataFlush() + } + + // MARK: - VersionedPlugin + + public static func version() -> String { _version } + + // MARK: - Private Methods + + private func makeBrazeConfiguration(from settings: BrazeSettings) -> Braze.Configuration? { + guard let endpoint = settings.customEndpoint else { return nil } + let configuration = Braze.Configuration(apiKey: settings.apiKey, endpoint: endpoint) + configuration.api.addSDKMetadata([.segment]) + configuration.api.sdkFlavor = .segment + return configuration + } + + private func makeBraze( + from settings: BrazeSettings, + configuration: Braze.Configuration + ) -> Braze { + additionalConfiguration?(configuration) + + let braze = Braze(configuration: configuration) + + #if canImport(BrazeUI) + if settings.automaticInAppMessageRegistrationEnabled == true { + inAppMessageUI = BrazeInAppMessageUI() + braze.inAppMessagePresenter = inAppMessageUI + } + #endif + + additionalSetup?(braze) + + return braze + } + + private func setAttributionData(properties: JSON?) { + let attributionData = Braze.User.AttributionData( + network: properties?.value(forKeyPath: "campaign.source"), + campaign: properties?.value(forKeyPath: "campaign.name"), + adGroup: properties?.value(forKeyPath: "campaign.ad_group"), + creative: properties?.value(forKeyPath: "campaign.as_creative") + ) + braze?.user.set(attributionData: attributionData) + } + + private func logPurchase(name: String, properties: [String: Any]) { + let currency: String = properties["currency"] as? String ?? "USD" + + // - Multiple products in a single event. + if let products = properties["products"] as? [[String: Any]] { + for var product in products { + // - Retrieve fields + let productId = product["productId"] as? String ?? "Unknown" + let price = extractRevenue(key: "price", from: product) ?? 0 + let quantity = product["quantity"] as? Int + // - Cleanup + product["productId"] = nil + product["price"] = nil + product["quantity"] = nil + // - Merge with root properties + let productProperties = properties.merging(product) { _, new in new } + // - Log + braze?.logPurchase( + productId: productId, + currency: currency, + price: price, + quantity: quantity ?? 0, + properties: productProperties + ) + } + return + } + + // - Regular purchase event. + let price = extractRevenue(key: "revenue", from: properties) ?? 0 + braze?.logPurchase( + productId: name, + currency: currency, + price: price, + properties: properties + ) + } + + private func logCustomEvent(name: String, properties: [String: Any]?) { + var properties = properties + properties?["revenue"] = nil + properties?["currency"] = nil + braze?.logCustomEvent(name: name, properties: properties) + } + + private func extractRevenue(key: String, from properties: [String: Any]?) -> Double? { + if let revenueDouble = properties?[key] as? Double { + return revenueDouble + } + + if let revenueString = properties?[key] as? String { + let revenueDouble = Double(revenueString) + return revenueDouble + } + + return nil + } + + private func log(message: String) { + analytics?.log(message: "[BrazeSegment] \(message)") + } + + // MARK: - Keys + + private enum Keys: String { + case installEventName = "Install Attributed" + case purchaseEventName1 = "Order Completed" + case purchaseEventName2 = "Completed Order" + + case subscriptionGroup = "braze_subscription_groups" + case subscriptionId = "subscription_group_id" + case subscriptionState = "subscription_state_id" + + static let maleTokens: Set = ["m", "male"] + static let femaleTokens: Set = ["f", "female"] + static let notApplicableTokens: Set = ["na", "not applicable"] + + static let reservedKeys: Set = [ + "birthday", + "email", + "firstName", + "lastName", + "gender", + "phone", + "address", + "anonymousId", + "userId", + Keys.subscriptionGroup.rawValue, + ] + } + +} + +// MARK: - Settings + +private struct BrazeSettings: Codable { + let apiKey: String + let customEndpoint: String? + let automaticInAppMessageRegistrationEnabled: Bool? + let logPurchaseWhenRevenuePresent: Bool? + + enum CodingKeys: String, CodingKey { + case apiKey + case customEndpoint + case automaticInAppMessageRegistrationEnabled = "automatic_in_app_message_registration_enabled" + case logPurchaseWhenRevenuePresent + } } diff --git a/Sources/SegmentBraze/Version.swift b/Sources/SegmentBraze/Version.swift index 6212f87..8df469e 100644 --- a/Sources/SegmentBraze/Version.swift +++ b/Sources/SegmentBraze/Version.swift @@ -1,16 +1,3 @@ -// -// Version.swift -// Segment -// -// Created by Brandon Sneed on 2/24/2022. -// - -// DO NOT MODIFY THIS FILE BY HAND!! -// DO NOT MODIFY THIS FILE BY HAND!! -// DO NOT MODIFY THIS FILE BY HAND!! -// DO NOT MODIFY THIS FILE BY HAND!! - -// Use release.sh's automation. - -// BREAKING.FEATURE.FIX -internal let __destination_version = "1.0.0" +extension BrazeDestination { + public static let _version = "2.0.0" +} diff --git a/Sources/SegmentBrazeUI b/Sources/SegmentBrazeUI new file mode 120000 index 0000000..5a581ec --- /dev/null +++ b/Sources/SegmentBrazeUI @@ -0,0 +1 @@ +SegmentBraze \ No newline at end of file diff --git a/release.sh b/release.sh deleted file mode 100755 index 33a8245..0000000 --- a/release.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash - -vercomp () { - if [[ $1 == $2 ]] - then - return 0 - fi - local IFS=. - local i ver1=($1) ver2=($2) - # fill empty fields in ver1 with zeros - for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) - do - ver1[i]=0 - done - for ((i=0; i<${#ver1[@]}; i++)) - do - if [[ -z ${ver2[i]} ]] - then - # fill empty fields in ver2 with zeros - ver2[i]=0 - fi - if ((10#${ver1[i]} > 10#${ver2[i]})) - then - return 1 - fi - if ((10#${ver1[i]} < 10#${ver2[i]})) - then - return 2 - fi - done - return 0 -} - -# check if `gh` tool is installed. -if ! command -v gh &> /dev/null -then - echo "Github CLI tool is required, but could not be found." - echo "Install it via: $ brew install gh" - exit 1 -fi - -# check if `gh` tool has auth access. -# command will return non-zero if not auth'd. -authd=$(gh auth status -t) -if [[ $? != 0 ]]; then - echo "ex: $ gh auth login" - exit 1 -fi - -# check that we're on the `main` branch -branch=$(git rev-parse --abbrev-ref HEAD) -if [ $branch != 'main' ] -then - echo "The 'main' must be the current branch to make a release." - echo "You are currently on: $branch" - exit 1 -fi - -versionFile="./sources/SegmentBraze/Version.swift" - -# get last line in version.swift -versionLine=$(tail -n 1 $versionFile) -# split at the = -version=$(cut -d "=" -f2- <<< "$versionLine") -# remove quotes and spaces -version=$(sed "s/[' \"]//g" <<< "$version") - -echo "$(basename $(pwd)) current version: $version" - -# no args, so give usage. -if [ $# -eq 0 ] -then - echo "Release automation script" - echo "" - echo "Usage: $ ./release.sh " - echo " ex: $ ./release.sh \"1.0.2\"" - exit 0 -fi - -newVersion="${1%.*}.$((${1##*.}))" -echo "Preparing to release $newVersion..." - -vercomp $newVersion $version -case $? in - 0) op='=';; - 1) op='>';; - 2) op='<';; -esac - -if [ $op != '>' ] -then - echo "New version must be greater than previous version ($version)." - exit 1 -fi - -read -r -p "Are you sure you want to release $newVersion? [y/N] " response -case "$response" in - [yY][eE][sS]|[yY]) - ;; - *) - exit 1 - ;; -esac - -# get the commits since the last release... -# note: we do this here so the "Version x.x.x" commit doesn't show up in logs. -changelog=$(git log --pretty=format:"- (%an) %s" $(git describe --tags --abbrev=0 @^)..@) -tempFile=$(mktemp) -#write changelog to temp file. -echo -e "$changelog" >> $tempFile - -# update sources/Segment/Version.swift -# - remove last line... -sed -i '' -e '$ d' $versionFile -# - add new line w/ new version -echo "internal let __destination_version = \"$newVersion\"" >> $versionFile - -# commit the version change. -git commit -am "Version $newVersion" && git push -# gh release will make both the tag and the release itself. -gh release create $newVersion -F $tempFile -t "Version $newVersion" - -# remove the tempfile. -rm $tempFile - -