diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9098820 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,66 @@ +name: Release + +on: + pull_request: + branches: + - main + types: + - closed + +permissions: + contents: write + pages: write + id-token: write + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + docs: + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - name: Publish Jazzy Docs + uses: steven0351/publish-jazzy-docs@v1 + with: + personal_access_token: ${{ secrets.ACCESS_TOKEN }} + branch: main + + release: + if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') + environment: Production + outputs: + tag: ${{ steps.semver.outputs.nextStrict }} + runs-on: macos-latest + steps: + - name: Checkout TIKI SDK iOS + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Version + id: semver + uses: ietf-tools/semver-action@v1 + with: + token: ${{ github.token }} + + - name: Create a new tag + run: | + git tag ${{ steps.semver.outputs.nextStrict }} + git push origin ${{ steps.semver.outputs.nextStrict }} + + - name: Deploy to Cocoapods + run: | + set -eo pipefail + pod lib lint --allow-warnings + pod trunk push TikiSdkDebug.podspec --allow-warnings + pod trunk push TikiSdkRelease.podspec --allow-warnings + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + + - name: Create a Release + continue-on-error: true + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.semver.outputs.nextStrict }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b9a70f3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Test + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + test: + runs-on: macos-latest + steps: + - name: Checkout TIKI SDK iOS + uses: actions/checkout@v4 + + - name: Setup XCode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Run pod linter + run: pod lib lint --allow-warnings + + - name: Setup workspace + run: | + cd Example + pod install --repo-update + + - name: Run tests + run: | + cd Example + xcodebuild test -workspace TikiClient.xcworkspace -scheme TikiClient-Example -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' \ No newline at end of file diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..6691d8d --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,55 @@ +name: Version + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + version: + if: startsWith(github.head_ref, 'release/') + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.semver.outputs.nextStrict }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Checkout PR + id: getpr + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh pr checkout ${{ github.event.pull_request.number }} + export PR_BRANCH=$(git branch --show-current) + echo "branch=$PR_BRANCH" >> $GITHUB_OUTPUT + + - name: Get Version + id: semver + uses: ietf-tools/semver-action@v1 + with: + token: ${{ github.token }} + branch: ${{ steps.getpr.outputs.branch }} + + - name: Update Podspec + run: | + sed -i 's/${{ steps.semver.outputs.current }}/${{ steps.semver.outputs.nextStrict }}/' TikiClient.podspec + sed -i 's/${{ steps.semver.outputs.current }}/${{ steps.semver.outputs.nextStrict }}/' README.md + + - name: Commit Changes + continue-on-error: true + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "action@github.com" + git config --global user.name "GH Action" + git add TikiClient.podspec + git add README.md + git commit -m 'ci: version bump' + git push \ No newline at end of file diff --git a/Example/Podfile b/Example/Podfile index d52e0cf..b5bd9a7 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,15 +1,13 @@ +platform :ios, '15.0' + source 'https://cdn.cocoapods.org/' use_frameworks! -platform :ios, '15.0' - target 'TikiClient_Example' do - pod 'TikiClient', :path => '../' + pod 'TikiClient', :path => '../' end - + target 'TikiClientExampleTests' do pod 'TikiClient', :path => '../' - pod 'CryptoSwift' - end - + end \ No newline at end of file diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 552e461..9ba793c 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,22 +1,13 @@ PODS: - - AppAuth (1.6.2): - - AppAuth/Core (= 1.6.2) - - AppAuth/ExternalUserAgent (= 1.6.2) - - AppAuth/Core (1.6.2) - - AppAuth/ExternalUserAgent (1.6.2): - - AppAuth/Core - - CryptoSwift (1.8.1) - - TikiClient (0.1.0): - - AppAuth + - CryptoSwift (1.8.2) + - TikiClient (0.0.2): - CryptoSwift DEPENDENCIES: - - CryptoSwift - TikiClient (from `../`) SPEC REPOS: trunk: - - AppAuth - CryptoSwift EXTERNAL SOURCES: @@ -24,10 +15,9 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 - CryptoSwift: b9c701d6f5011df23794dbf7f2e480a77835d83d - TikiClient: 3a91dc7c2954d54e92f8a1f2f132844c29a50cbb + CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea + TikiClient: a267c74ffcf7ebe39e869e602d26caf05aebeeb6 -PODFILE CHECKSUM: 7501e71a92a5f280b97aaf5f199fa519634e934c +PODFILE CHECKSUM: 40b4e084f57008f1517e1137525ad282b8756281 COCOAPODS: 1.15.2 diff --git a/Example/TikiClient.xcodeproj/project.pbxproj b/Example/TikiClient.xcodeproj/project.pbxproj index fe6cba9..9060964 100644 --- a/Example/TikiClient.xcodeproj/project.pbxproj +++ b/Example/TikiClient.xcodeproj/project.pbxproj @@ -7,11 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 31A93A8A9C1FE6546E52D4AD /* Pods_TikiClient_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99997F9EA2E68CC22E9BC31D /* Pods_TikiClient_Example.framework */; }; + 632694CF57FA702626EC7733 /* Pods_TikiClientExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFF57A86C217F3BB10B27DE9 /* Pods_TikiClientExampleTests.framework */; }; 886417CD2B62B1F40088A07C /* Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = 886417CC2B62B1F40088A07C /* Example.swift */; }; 888188F12B603B1C006DE4CE /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 888188F02B603B1C006DE4CE /* Security.framework */; }; 88E1B7C82B926458005429F1 /* TikiClientExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E1B7C72B926458005429F1 /* TikiClientExampleTests.swift */; }; - C337CE9336F6DA1302FD5900 /* Pods_TikiClientExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7B6B338594F5A37E754066E /* Pods_TikiClientExampleTests.framework */; }; - E109A5293EEF29B311855F8C /* Pods_TikiClient_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F02D43BBF21874BF12F34C8 /* Pods_TikiClient_Example.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -25,22 +25,22 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 0F02D43BBF21874BF12F34C8 /* Pods_TikiClient_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TikiClient_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5591FAB57ACCBC4A5E132A54 /* Pods-TikiClientExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClientExampleTests.release.xcconfig"; path = "Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests.release.xcconfig"; sourceTree = ""; }; + 4F1022E4E88DA068E46CA9AD /* Pods-TikiClient_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClient_Example.release.xcconfig"; path = "Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example.release.xcconfig"; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* TikiClient_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TikiClient_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6A7490B63407C2915CE40FFF /* Pods-TikiClient_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClient_Example.debug.xcconfig"; path = "Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example.debug.xcconfig"; sourceTree = ""; }; + 6086BFF71EA3FDF2719A945A /* Pods-TikiClientExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClientExampleTests.release.xcconfig"; path = "Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests.release.xcconfig"; sourceTree = ""; }; 7E57A12C03C6615AFB2BF7E0 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 883FBB392BBDE493000F04F4 /* TikiClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TikiClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 886417CC2B62B1F40088A07C /* Example.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example.swift; sourceTree = ""; }; 888188F02B603B1C006DE4CE /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 88E1B7C52B926458005429F1 /* TikiClientExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TikiClientExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88E1B7C72B926458005429F1 /* TikiClientExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TikiClientExampleTests.swift; sourceTree = ""; }; + 99997F9EA2E68CC22E9BC31D /* Pods_TikiClient_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TikiClient_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A312AB41962D3733F97980EA /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - C560F6996EB813B6C74DACD6 /* Pods-TikiClient_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClient_Example.release.xcconfig"; path = "Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example.release.xcconfig"; sourceTree = ""; }; + B323433D56A551540495F805 /* Pods-TikiClient_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClient_Example.debug.xcconfig"; path = "Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example.debug.xcconfig"; sourceTree = ""; }; + DD52C0B59CB2E2BB8A025DA3 /* Pods-TikiClientExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClientExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests.debug.xcconfig"; sourceTree = ""; }; E3D3746125E016CC08382E85 /* TikiClient.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = TikiClient.podspec; path = ../TikiClient.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - E4D1B068E4726E7872C68701 /* Pods-TikiClientExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TikiClientExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests.debug.xcconfig"; sourceTree = ""; }; - E7B6B338594F5A37E754066E /* Pods_TikiClientExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TikiClientExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FFF57A86C217F3BB10B27DE9 /* Pods_TikiClientExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TikiClientExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,7 +49,7 @@ buildActionMask = 2147483647; files = ( 888188F12B603B1C006DE4CE /* Security.framework in Frameworks */, - E109A5293EEF29B311855F8C /* Pods_TikiClient_Example.framework in Frameworks */, + 31A93A8A9C1FE6546E52D4AD /* Pods_TikiClient_Example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -57,7 +57,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C337CE9336F6DA1302FD5900 /* Pods_TikiClientExampleTests.framework in Frameworks */, + 632694CF57FA702626EC7733 /* Pods_TikiClientExampleTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -124,10 +124,10 @@ B5408F1A29BE29310354D039 /* Pods */ = { isa = PBXGroup; children = ( - E4D1B068E4726E7872C68701 /* Pods-TikiClientExampleTests.debug.xcconfig */, - 5591FAB57ACCBC4A5E132A54 /* Pods-TikiClientExampleTests.release.xcconfig */, - 6A7490B63407C2915CE40FFF /* Pods-TikiClient_Example.debug.xcconfig */, - C560F6996EB813B6C74DACD6 /* Pods-TikiClient_Example.release.xcconfig */, + B323433D56A551540495F805 /* Pods-TikiClient_Example.debug.xcconfig */, + 4F1022E4E88DA068E46CA9AD /* Pods-TikiClient_Example.release.xcconfig */, + DD52C0B59CB2E2BB8A025DA3 /* Pods-TikiClientExampleTests.debug.xcconfig */, + 6086BFF71EA3FDF2719A945A /* Pods-TikiClientExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -137,8 +137,8 @@ children = ( 883FBB392BBDE493000F04F4 /* TikiClient.framework */, 888188F02B603B1C006DE4CE /* Security.framework */, - E7B6B338594F5A37E754066E /* Pods_TikiClientExampleTests.framework */, - 0F02D43BBF21874BF12F34C8 /* Pods_TikiClient_Example.framework */, + 99997F9EA2E68CC22E9BC31D /* Pods_TikiClient_Example.framework */, + FFF57A86C217F3BB10B27DE9 /* Pods_TikiClientExampleTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -150,12 +150,12 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "TikiClient_Example" */; buildPhases = ( - 84F66D6FB8571FC320331780 /* [CP] Check Pods Manifest.lock */, + BEEE526CCAC245DB022C0DCB /* [CP] Check Pods Manifest.lock */, 607FACCC1AFB9204008FA782 /* Sources */, 607FACCD1AFB9204008FA782 /* Frameworks */, 607FACCE1AFB9204008FA782 /* Resources */, 88C59FB72B8E65370040D717 /* ShellScript */, - 97B81E020C40E3902C442C1D /* [CP] Embed Pods Frameworks */, + 16ABD11F728595DD465BA0E3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -170,11 +170,11 @@ isa = PBXNativeTarget; buildConfigurationList = 88E1B7CB2B926458005429F1 /* Build configuration list for PBXNativeTarget "TikiClientExampleTests" */; buildPhases = ( - 546F111A767924FBA9FA125C /* [CP] Check Pods Manifest.lock */, + 9783531AC293B7E3C394F159 /* [CP] Check Pods Manifest.lock */, 88E1B7C12B926458005429F1 /* Sources */, 88E1B7C22B926458005429F1 /* Frameworks */, 88E1B7C32B926458005429F1 /* Resources */, - 8A82B0B6FE1A43084A73EFDC /* [CP] Embed Pods Frameworks */, + 222AF2E5C449B7E1CD52B88B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -247,48 +247,44 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 546F111A767924FBA9FA125C /* [CP] Check Pods Manifest.lock */ = { + 16ABD11F728595DD465BA0E3 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", + "${BUILT_PRODUCTS_DIR}/TikiClient/TikiClient.framework", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TikiClientExampleTests-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TikiClient.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 84F66D6FB8571FC320331780 /* [CP] Check Pods Manifest.lock */ = { + 222AF2E5C449B7E1CD52B88B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", + "${BUILT_PRODUCTS_DIR}/TikiClient/TikiClient.framework", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TikiClient_Example-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TikiClient.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 88C59FB72B8E65370040D717 /* ShellScript */ = { @@ -308,48 +304,48 @@ shellPath = /bin/sh; shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; - 8A82B0B6FE1A43084A73EFDC /* [CP] Embed Pods Frameworks */ = { + 9783531AC293B7E3C394F159 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework", - "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", - "${BUILT_PRODUCTS_DIR}/TikiClient/TikiClient.framework", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TikiClient.framework", + "$(DERIVED_FILE_DIR)/Pods-TikiClientExampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TikiClientExampleTests/Pods-TikiClientExampleTests-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 97B81E020C40E3902C442C1D /* [CP] Embed Pods Frameworks */ = { + BEEE526CCAC245DB022C0DCB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework", - "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", - "${BUILT_PRODUCTS_DIR}/TikiClient/TikiClient.framework", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TikiClient.framework", + "$(DERIVED_FILE_DIR)/Pods-TikiClient_Example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TikiClient_Example/Pods-TikiClient_Example-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -485,9 +481,10 @@ }; 607FACF01AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6A7490B63407C2915CE40FFF /* Pods-TikiClient_Example.debug.xcconfig */; + baseConfigurationReference = B323433D56A551540495F805 /* Pods-TikiClient_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; DEVELOPMENT_TEAM = JC63RM5U2M; INFOPLIST_FILE = TikiClient/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "The app need the camera permision"; @@ -503,9 +500,10 @@ }; 607FACF11AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C560F6996EB813B6C74DACD6 /* Pods-TikiClient_Example.release.xcconfig */; + baseConfigurationReference = 4F1022E4E88DA068E46CA9AD /* Pods-TikiClient_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; DEVELOPMENT_TEAM = JC63RM5U2M; INFOPLIST_FILE = TikiClient/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "The app need the camera permision"; @@ -521,7 +519,7 @@ }; 88E1B7CC2B926458005429F1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E4D1B068E4726E7872C68701 /* Pods-TikiClientExampleTests.debug.xcconfig */; + baseConfigurationReference = DD52C0B59CB2E2BB8A025DA3 /* Pods-TikiClientExampleTests.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -532,7 +530,7 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -558,7 +556,7 @@ }; 88E1B7CD2B926458005429F1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5591FAB57ACCBC4A5E132A54 /* Pods-TikiClientExampleTests.release.xcconfig */; + baseConfigurationReference = 6086BFF71EA3FDF2719A945A /* Pods-TikiClientExampleTests.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -569,7 +567,7 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/Example/TikiClient.xcodeproj/xcshareddata/xcschemes/TikiClient-Example.xcscheme b/Example/TikiClient.xcodeproj/xcshareddata/xcschemes/TikiClient-Example.xcscheme index 27721b6..6bc85ea 100644 --- a/Example/TikiClient.xcodeproj/xcshareddata/xcschemes/TikiClient-Example.xcscheme +++ b/Example/TikiClient.xcodeproj/xcshareddata/xcschemes/TikiClient-Example.xcscheme @@ -1,6 +1,6 @@ String`: Initiates the process of scanning a physical receipt and returns the receipt ID. - - `login(provider: EmailProviderEnum)`: Initiates the process of scraping receipts from emails. - - `logout(email: String)`: Removes a previously added email account. - - `accounts() -> [EmailAccount]`: Retrieves the list of connected email accounts. - - `scrape()`: Initiates the process of scraping receipts from emails. - - `card(last4: String, bin: String, issuer: String, network: String)`: Adds a card for card-linked offers. - - `offers() -> [Offer]`: Retrieves card-linked offers for the user. - - `transaction(transaction: Transaction)`: Submits a transaction for card-linked offer matching. - - `rewards() -> [Reward]`: Retrieves information about the user's rewards. - - `widget(theme: Theme?)`: Displays the widget for pre-built UIs with a custom theme. +### Initialization -### TikiClient.auth +This method authenticates with the TIKI API and registers the user's device to publish data. It is an asynchronous method due to the necessary API calls. -- **Methods:** - - `authenticate() -> String`: Authenticates with TIKI and saves the auth and refresh tokens. - - `token() -> String`: Retrieves the authentication token, refreshing if necessary. - - `revoke()`: Revokes the authentication token. - - `refresh() -> String`: Refreshes the authentication token. +The user ID can be any arbitrary string that identifies the user in the application using the client library. It is not recommended to use personally identifiable information, such as emails. If necessary, use a hashed version of it. -### TikiClient.license +```swift +TikiClient.initialize("") +``` -- **Methods:** - - `create() -> LicenseRecord`: Creates a new license for the user. - - `get() -> License`: Retrieves the user's active license. - - `revoke() -> License`: Revokes the user's existing license. - - `verify() -> Bool`: Verifies the validity of the user's license. +To switch the active user, call the `TikiClient.initialize` method again. -### Email +### Data License -- **Methods:** - - `login(provider: EmailProvider)`: Authenticates with OAuth and adds an email account for scraping receipts. - - `accounts() -> [EmailAccount]`: Retrieves the list of connected email accounts. - - `logout(email: String)`: Removes a previously added email account. +To successfully capture and upload receipt data to our platform, it is essential to establish a valid User Data License Agreement (UDLA). This agreement serves as a clear, explicit addendum to your standard app terms of service, delineating key aspects: -### TikiClient.capture +- User Ownership: It explicitly recognizes the user as the rightful owner of the data. +- Usage Terms: It outlines the terms governing how the data will be licensed and used. +- Compensation: It defines the compensation arrangements offered in exchange for the provided data. -- **Methods:** - - `camera() -> UIImage`: Captures an image of a receipt for processing. - - `email(onPublish: (receiptId: String) -> Void)`: Downloads potential receipt data from known receipt email senders and publishes it. - - `publish(data: ImageData) -> String`: Uploads receipt images or email data for receipt data extraction. - - `status(receiptId: String) -> PublishingStatus`: Checks the publishing status of the data. +Our Client Library streamlines this process by providing a pre-qualified agreement, filled with the company information provided in the library configuration. -### TikiClient.clo +Retrieve the formatted terms of the license, presented in Markdown, using the `TikiClient.terms()` method. This allows you to present users with a clear understanding of the terms before they agree to license their data. This agreement comes , ensuring a seamless integration into the license registry. -- **Methods:** - - `card(last4: String, bin: String, issuer: String, network: String)`: Adds a card to the user's account. - - `offers() -> [Offer]`: Retrieves card-linked offers for the user. - - `rewards() -> [Reward]`: Retrieves information about the user's rewards. - - `transaction(transaction: Transaction)`: Sends transaction information to match card-linked offers. +Upon user agreement, generate the license using the `TikiClient.createLicense` method. -## Documentation +```swift +let license = TikiClient.createLicense() +``` -The library should provide comprehensive documentation including: +This method needs to be invoked once for each device. Once executed, the license is registered in TIKI storage, eliminating the need to recreate it in the future. -1. **Getting Started Guide:** Instructions on how to integrate the library into an iOS project. -2. **API Reference:** Detailed documentation for each class, method, and parameter. -3. **Sample Code:** Examples demonstrating how to use key functionalities of the library. -4. **Configuration Guide:** Guidelines on setting up TIKI credentials and other necessary configurations. -5. **Error Handling:** Information on handling errors and exceptions. -6. **Example App** An example app with basic UI, for manual testing. +### Data Capture -## Non-Native Dependencies +The Client Library offers an optional method for scanning physical receipts via the mobile device camera. -- AppAuth for OAuth2. \ No newline at end of file +Use the `TikiClient.scan()` method to initiate the receipt scanning process. This method does not directly return the scanned receipt data. Instead, it provides the data through a callback function that you supply. + +Here's an example of how to use it: + +```swift +TikiClient.scan() { image: UIImage -> + // Handle the scanned bitmap here +} +``` + +In this example, `image` is the scanned receipt, and the code inside the callback function (i.e., `// Handle the scanned bitmap here`) is where you can process or use the scanned data. + +### Data Upload + +Utilize the `TikiClient.publish` method to upload receipt images to TIKI for processing. This method is versatile, as it can receive results from the `TikiClient.scan` method, or your application can implement a custom scan extraction method, sending the results to `TikiClient.publish`. + +The `publish` method accepts a bitmap image or an array of bitmap images, providing flexibility to capture and scan multiple images, ideal for processing lengthy receipts. + +```swift +let data: UIImage = ... // The scanned receipt +let result = TikiClient.publish(data) +``` + +Upon execution, this method returns a `CompletableDeferred` object that will be completed when the data has been published. + +### Retrieve Results + +Once you've uploaded receipt images to TIKI for processing using the `TikiClient.publish` method, you can retrieve the extracted data associated with a specific receipt by calling the `TikiClient.receipt(receiptId)` method. + +```swift +// Assuming you have the receiptId stored in a variable named 'receiptId' +let receiptData = await TikiClient.receipt(receiptId); +print(receiptData); +``` + +**Note**: The data extraction from receipts is performed asynchronously by Amazon Textract. Processing typically takes a few seconds, but it can occasionally take up to a minute. It's important to note that making subsequent calls to `TikiClient.receipt(receiptId)` shortly after using `TikiClient.publish` might lead to unexpected results and false `404` errors from the API. We recommend allowing sufficient time for the extraction process to complete before attempting to retrieve the extracted data. + + +## API Reference + +The central API interface in the library is the `TikiClient` object, designed to abstract the complexities of authorization and API requests. While serving as the primary entry point, it's important to note that all APIs within the library are public and accessible. + +For detailed usage instructions, please consult the TIKI Client API Documentation. This comprehensive resource provides direct insights into utilizing the various functionalities offered by the TIKI Client Library. \ No newline at end of file diff --git a/TikiClient.podspec b/TikiClient.podspec index 886e248..3e6b2dd 100644 --- a/TikiClient.podspec +++ b/TikiClient.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TikiClient' - s.version = '0.1.0' + s.version = '0.0.2' s.summary = 'Lean Cocoapods lib for seamless integration with TIKI Rest APIs' - + s.swift_version = '5.9' s.description = <<-DESC The TIKI APIs comprise a set of HTTP REST APIs designed for seamless integration with any standard HTTP client. The Client Libraries serve as a user-friendly layer around the TIKI APIs, @@ -25,9 +25,8 @@ Pod::Spec.new do |s| s.source_files = 'TikiClient/Classes/**/*' - s.dependency 'AppAuth' s.dependency 'CryptoSwift' - + s.resource_bundles = { 'TikiClientAssets' => [ 'TikiClient/Assets' diff --git a/TikiClient/Classes/Capture/CaptureService.swift b/TikiClient/Classes/Capture/CaptureService.swift index 8e3a641..1588df5 100644 --- a/TikiClient/Classes/Capture/CaptureService.swift +++ b/TikiClient/Classes/Capture/CaptureService.swift @@ -35,7 +35,7 @@ public class CaptureService { if let imageData = image.jpegData(compressionQuality: 1.0) { let task = URLSession.shared.uploadTask(with: request, from: imageData) { data, response, error in if error == nil { - let httpResponse = response as? HTTPURLResponse + response as? HTTPURLResponse completion(id, nil) return } diff --git a/TikiClient/Classes/Key/KeyRepository.swift b/TikiClient/Classes/Key/KeyRepository.swift index 93ee20c..d69bd2d 100644 --- a/TikiClient/Classes/Key/KeyRepository.swift +++ b/TikiClient/Classes/Key/KeyRepository.swift @@ -11,7 +11,6 @@ public final class KeyRepository { func save(_ data: Data, service: String, key: String) { - // Create query let query = [ kSecValueData: data, kSecClass: kSecClassGenericPassword, @@ -19,11 +18,9 @@ public final class KeyRepository { kSecAttrAccount: key, ] as CFDictionary - // Add data in query to keychain let status = SecItemAdd(query, nil) if status == errSecDuplicateItem { - // Item already exist, thus update it. let query = [ kSecAttrService: bundle + service, kSecAttrAccount: key, @@ -32,12 +29,10 @@ public final class KeyRepository { let attributesToUpdate = [kSecValueData: data] as CFDictionary - // Update existing item SecItemUpdate(query, attributesToUpdate) } if status != errSecSuccess { - // Print out the error print("Error: \(status)") } } @@ -76,7 +71,7 @@ public final class KeyRepository { ] var result: CFTypeRef? - let operationStatus = SecItemCopyMatching(query as CFDictionary, &result) + SecItemCopyMatching(query as CFDictionary, &result) let resultArray = result as? [NSDictionary] resultArray?.forEach { item in @@ -96,13 +91,11 @@ public final class KeyRepository { kSecClass: kSecClassGenericPassword, ] as CFDictionary - // Delete item from keychain SecItemDelete(query) } func update(_ data: Data, service: String, key: String) { - // Item already exist, thus update it. let query = [ kSecAttrService: bundle + service, kSecAttrAccount: key, @@ -111,8 +104,7 @@ public final class KeyRepository { let attributesToUpdate = [kSecValueData: data] as CFDictionary - // Update existing item - let error = SecItemUpdate(query, attributesToUpdate) + SecItemUpdate(query, attributesToUpdate) } } diff --git a/TikiClient/Classes/License/LicenseRequest.swift b/TikiClient/Classes/License/LicenseRequest.swift index 1fc865d..5f2d549 100644 --- a/TikiClient/Classes/License/LicenseRequest.swift +++ b/TikiClient/Classes/License/LicenseRequest.swift @@ -13,7 +13,7 @@ public struct LicenseRequest : Codable { let description: String let origin: String var signature: String - let expiry: String? = nil + var expiry: String? = nil } diff --git a/TikiClient/Classes/TikiClient.swift b/TikiClient/Classes/TikiClient.swift index ab892ad..62cfa76 100644 --- a/TikiClient/Classes/TikiClient.swift +++ b/TikiClient/Classes/TikiClient.swift @@ -37,7 +37,7 @@ public class TikiClient { if(key == nil){ auth.registerAddress(userId: userId, providerId: TikiClient.config!.providerId, pubKey: TikiClient.config!.publicKey, completion: {address, error in - guard let userAddress = address else { + guard address != nil else { completion("Register Adress Error") return } diff --git a/terms.md b/terms.md deleted file mode 100644 index db1766d..0000000 --- a/terms.md +++ /dev/null @@ -1,101 +0,0 @@ -# User Data License Agreement - -This is an agreement between **you, the *"User",*** and **{{{COMPANY}}}, the *"Company"***, the licensor. The agreement, the User Data License Agreement (*"UDLA"*), sets out both parties rights and obligations in respect to the licensing and use of the data referred to within. - -The terms of the UDLA are relevant for and apply only to those Users and licensors who have elected to participate in the data licensing program detailed within this agreement. The terms and conditions of this agreement are to be read as a whole, and each clause is deemed essential to the agreement. - -Your participation in this data licensing program is voluntary and will not affect your rights as a consumer or customer of our products and services in any way. - -## 1. Data Use - -By participating in the Company's data licensing program, you, the User, are granting us, the Company, the right to use and share certain data about you (*"Shared Information"*) with the carefully selected partners and customers as described in this Section. In exchange for you granting us this right, we agree to compensate you as described in Section 3. - -In addition, we contractually require any selected partners and customers receiving your Shared Information to not further share, disclose, or use it for any other purpose than the purposes described in this Section. - -### A. Shared Information - -The following details the types of data collected (*"Collected Data"*), the purposes for which the data may be utilized (*"Use Cases"*), and optionally where the data may be stored or processed (*"Destinations"*). In aggregate, this data is the Shared Information. - -#### Collected Data -* *User ID* - Such as screen name, handle, account ID, assigned user ID, customer number, or other user- or account-level ID that can be used to identify a particular user or account. -* *Purchase History* - An account’s or individual’s purchases or purchase tendencies. - -#### Use Cases -* *AI training* - train machine learning models with user data. -* *Analytics* - extract insights and signals from user data. -* *Attribution* - determine the actions that led to an outcome. -* *Distribution* - distribute/re-license raw data, insights, signals, etc., to 3rd-parties. -* *Evaluation* - market test new potential uses cases and compensation opportunities. -* *Personalization* - tailor messaging, offers, features, etc., to an individual. -* *Retargeting* - advertise to and reach users, often on other platforms. -* *Support* - add user data into customer support processes. - -#### Destinations -* *Tiki inc. (mytiki.com)* - the Company's zero-party data processor. - -## 2. Compensation - -We, the Company, agree to compensate you, the User, with the compensation described below for granting us a license to use your Shared Information listed in Section 1.A. - -Any changes in compensation require a new agreement. This will have the effect of cancelling and voiding this and any earlier agreements or licenses. - -### A. Program - -We, the Company, agree to compensate you, the User, for granting us a license to use your Shared Information in exchange for **$1 USD per month**, available for withdrawal at the end of each month period, contingent upon the following criteria: - -* A Gmail account you own, connected to the app, for the duration of the period. Gmail connectivity status is available in-app. -* A Retailer account you own, connected to the app, for the duration of the period. See app for the complete list of supported Retailer accounts and connectivity status. -* Opening the app once each calendar week, to synchronize your connected accounts, for the duration of the period. Synchronization status and information is available in-app. -* 5 new receipts for items purchased from supported merchants within the period. Receipts are counted across all connected Retailer and Gmail accounts. Receipt status is available in-app. - -Cash withdrawal may be subject to minimum balance thresholds, participation durations, and other application restrictions. - -### B. Termination - -If an opt-out or termination occurs, no new Shared Information will be exchanged for rewards. Pre-existing earned rewards will not be affected. Refer to Section 5 for additional termination details. - -### C. Requirements - -This offer is contingent upon you the User, providing reasonable access to the Shared Information through the use of one or more of our products and services. Failure to do so, or attempts to mask, obfuscate, or manipulate the Shared Information may terminate this agreement. - -## 3. Privacy and Confidentiality - -When you, the User, agree to the data licensing program, you agree to the collection of Shared Information by the Company, which may include zero, first, and third-party data covered under our [Terms of Service]({{{TOS}}}) and [Privacy Policy]({{{POLICY}}}). - -This agreement does not replace our Terms of Service or Privacy Policy and any rights or representations unless otherwise explicitly stated extend to this agreement, including but not limited to GDPR, CCPA, and PIPEDA considerations. - -While most Shared Information is not Personally Identifiable Information, it may include information about your account (such as user ids or email address) and information about your computer or device (such as IP address or operating system). Refer Section 1.A for specifics. - -## 4. Duration - -Unless otherwise specified under Section 2, this agreement to license your Shared Information will be considered perpetual. - -## 5. Termination - -Any amendments or alterations to this agreement will render it void. If the Company wishes to change, amend or alter the agreement in any way, the Company will notify you, the User of the new offer. This subsequent offer and, if you accept it, the new UDLA will replace any and all earlier UDLAs between you, the User and the Company. - -The Company may decide to terminate the agreement at any time, for any reason, by notifying you, the User of their intention to terminate. - -### A. User Opt Out - -You, the User, may choose to opt out of this program at any time, by disconnecting any accounts you've connected within the application. The choice to do so will have no impact on any other services the Company provide you, and the Company will not hold it against you in any way. - -Upon receiving your opt-out notice, the Company will stop all future collection of Shared Information as described in Section 1.A as soon as possible. The Company may continue to use Shared Information licensed prior to the opt-out. - -## 6. Governing Law - -The governing law of this agreement **{{{JURISDICTION}}}**, where the company is located or headquartered. - -## 7. Data Ownership - -You, the User, agree that the Shared Information is your data, you are the legal owner, and that you are allowed to share such data. - -You cannot participate in the program if you are underage. If this is the case, the agreement will be immediately rendered void. - -## 8. Limitation of Liability - -Each party's liability to the other parties for any loss, cost, claim, injury, liability, or expense, including reasonable attorney's fees, relating to, or arising from any act or omission in its performance of this agreement, shall be limited to the amount of direct damage actually incurred. In no event shall any party be liable to the other parties for any indirect, special, consequential, or punitive damages. - -## 9. Third-Party Indemnity - -You, the User, and the Company agree to defend, indemnify, and hold harmless Tiki Inc., and its directors, officers, employees, and agents from and against any and all third-party claims, demands, and liabilities, including reasonable attorneys fees, resulting from or arising out of (i) the Services provided under this Agreement actually or allegedly infringing or violating any patents, copyrights, trade secrets, licenses, or other intellectual property rights of a third-party; (ii) any breach of the Company's representations and warranties in this Agreement; or (iii) The Company's failure to comply with the Company's obligations under any and all laws, rules or regulations applicable to the Company or the Services provided under this Agreement. \ No newline at end of file