diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..39c0d1e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,74 @@ +--- +assignees: [] +body: + - + attributes: + description: "What is the problem? A clear and concise description of what the bug is." + label: "Describe the bug" + placeholder: "Tell us what you see!" + id: description + type: textarea + validations: + required: true + - + attributes: + description: "Please provide as much step-by-step detail as possible including logs, stack traces, and uncaught exceptions." + label: "Steps to reproduce" + value: | + 1. + 2. + 3. + id: steps + type: textarea + validations: + required: true + - + attributes: + description: "What did you expect to happen?" + label: "Expected behavior" + id: expected + type: textarea + validations: + required: true + - + attributes: + description: "What version of sdk are you seeing this issue on?" + label: "SDK Version" + placeholder: "5.2.0" + id: sdk-version + type: input + validations: + required: true + - + attributes: + description: "What devices or emulators are you seeing this bug on?" + label: Make and Model + placeholder: "iPhone 13 / Samsung S21" + id: device + type: input + validations: + required: true + - + attributes: + description: "What version of the device OS?" + label: OS + placeholder: "iOS 15.6.1 / Android 12" + id: os + type: input + validations: + required: true + - + attributes: + description: "Anything else that might be relevant for troubleshooting this bug. Any screenshots or videos that show the issue are very helpful." + label: "Additional Information/Context" + id: context + type: textarea + validations: + required: false + +description: "Found a bug in the Branch Cordova SDK? File it here." +labels: + - bug + - needs-triage +name: "🐞 Bug report" +title: "(short issue description)" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..aa56f767 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +--- +blank_issues_enabled: false +contact_links: + - name: "📕 Documentation Issue" + url: https://help.branch.io/developers-hub/docs/cordova-phonegap-ionic + about: Report an issue in the Branch Cordova SDK Reference documentation by clicking "Suggest edits" button on the documentation page. + - name: "Branch Support" + url: https://help.branch.io/using-branch/page/submit-a-ticket + about: If you are having general trouble with Branch Cordova SDK integration, please submit a ticket to Branch Support. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..c8832d1b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,14 @@ +--- +name: 🚀 Feature Request +description: Suggest an idea for this project +title: "(short issue description)" +labels: [feature-request, needs-triage] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Describe the feature + description: A clear and concise description of the feature you are proposing. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..d43e2e43 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Reference +SDK-XXXX -- . + +## Summary +<!-- Simple summary of what was changed. --> + +## Motivation +<!-- Why are you making this change? If it's for fixing a bug, if possible, please include a code snippet or example project that demonstrates the issue. --> + +## Type Of Change +<!-- Please delete options that are not relevant --> +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## Testing Instructions +<!-- Testing instructions, example code snippets, etc --> + + +<!-- Checklist --> +<!-- My code follows the style guidelines of this project --> +<!-- I have performed a self-review of my code --> +<!-- I have commented my code, particularly in hard-to-understand areas --> +<!-- I have made corresponding changes to the documentation --> +<!-- I have added tests that prove my fix is effective or that my feature works --> +<!-- New and existing unit tests pass locally with my changes --> + +cc @BranchMetrics/saas-sdk-devs for visibility. diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..e69de29b diff --git a/.github/workflows/automation-trigger-test.yml b/.github/workflows/automation-trigger-test.yml new file mode 100644 index 00000000..146899ab --- /dev/null +++ b/.github/workflows/automation-trigger-test.yml @@ -0,0 +1,18 @@ +#on: [push] +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + github-token: ${{ secrets.BRANCHLET_ACCESS_TOKEN_PUBLIC }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'BranchMetrics', + repo: 'qentelli-saas-sdk-cordova-testing-automation', + workflow_id: 'cordova-manual.yml', + ref: 'master' + }) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..4ae1061a --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,28 @@ +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.STALE_PERSONAL_ACCESS_TOKEN }} + days-before-issue-stale: 60 + days-before-close: 7 + stale-issue-message: 'This issue has been automatically marked as stale due to inactivity for 60 days. If this issue is still relevant, please respond with any updates or this issue will be closed in 7 days. If you believe this is a mistake, please comment to let us know. Thank you for your contributions.' + stale-issue-label: 'no-issue-activity' + close-issue-message: 'This issue has been closed due to inactivity. If this issue is still relevant, please reopen it or create a new one. Thank you for your contributions.' + start-date: '2023-05-22' diff --git a/.gitignore b/.gitignore index f3485004..156a3967 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ npm-debug.log* yarn-error.log* .vscode .idea +cordova-ionic-phonegap-branch-deep-linking-attribution.iml src/android/.idea \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 55df6eac..08b94cbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +5.2.1 Jan 4, 2023 +* Fix Javascript method setLogging to enable logging in the native layer. +* Update Android SDK to 5.2.7 +* Update iOS SDK to 1.45.2 + +5.2.0 August 8th, 2022 +* Update iOS SDK to 1.43.1 +* Update Android SDK to 5.2.0 +* Added method to generate Branch QR codes, getBranchQRCode(). + +5.1.0 May 27, 2022 +* Update iOS SDK to 1.42.0 +* Update Android SDK to 5.1.5 +* Update 3rd party dependencies. Of note the plist vulnerability. (Thanks Sujay-shetty) +* Replace setDebug with setLogging and test devices. https://help.branch.io/using-branch/docs/adding-test-devices + +5.0.2 February 9, 2022 +* Update dependencies to latest non-breaking versions, of note the shelljs vulnerability. (Thanks again Sujay-shetty!) + +5.0.1 February 8, 2022 +* Remove request package (thanks for catching Sujay-shetty) + +5.0.0 January 21, 2022 +* Add content items support in sendBranchEvent +* Remove sendCommerceEvent +* Fix bug where custom data would clobber other fields when creating event in Android plugin +* Update iOS SDK to 1.40.2 +* Update Android SDK to 5.0.15 + +4.2.4 - May 3, 2021 + +CORE-1898 correct iOS API signature for LATD +Fix alternate link domain issue - Thanks MaximBelov +Fix exist check - Thanks MaximBelov + +4.2.3 - April 29, 2021 + +Update Android SDK to 5.0.8 + +4.2.2 - April 28, 2021 + +Update iOS SDK to 1.39.2 +Update Android SDK to 5.0.7 + # [4.2.1](https://github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking/compare/v4.2.0...v4.2.1) (2020-11-05) # [4.2.0](https://github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking/compare/v4.1.3...v4.2.0) (2020-8-26) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..3ef2448a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability +If you discover a potential security issue in this project we ask that you notify Branch Security directly via email to security@branch.io +Please do not open GitHub issues or pull requests - this makes the problem immediately visible to everyone, including malicious actors. diff --git a/package.json b/package.json index 7e8a2790..76e277b1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "branch-cordova-sdk", "description": "Branch Metrics Cordova SDK", "main": "src/index.js", - "version": "4.2.2", + "version": "5.2.1", "homepage": "https://github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking", "repository": { "type": "git", @@ -55,24 +55,23 @@ ] }, "dependencies": { - "fs": "0.0.1-security", - "glob": "^7.1.4", + "glob": "^8.0.3", "mkpath": "^1.0.0", - "node-version-compare": "^1.0.1", - "plist": "^3.0.1", - "request": "^2.85.0", - "shelljs": "^0.8.3", - "xcode": "^2.0.0", - "xml2js": "^0.4.19" + "node-version-compare": "^1.0.3", + "plist": "^3.0.5", + "shelljs": "^0.8.5", + "xcode": "^3.0.1", + "xml2js": "^0.4.23" }, "devDependencies": { - "@commitlint/cli": "^8.3.5", - "@commitlint/config-conventional": "^8.3.4", - "eslint": "^6.8.0", - "eslint-config-airbnb-base": "^14.1.0", - "eslint-plugin-import": "^2.20.2", - "husky": "^4.2.5", - "lint-staged": "^10.1.7", - "prettier": "^2.0.5" + "fs": "0.0.1-security", + "@commitlint/cli": "^17.0.1", + "@commitlint/config-conventional": "^17.0.0", + "eslint": "^8.16.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.26.0", + "husky": "^8.0.1", + "lint-staged": "^12.4.2", + "prettier": "^2.6.2" } } diff --git a/plugin.xml b/plugin.xml index 87a6f95b..0ac900f8 100644 --- a/plugin.xml +++ b/plugin.xml @@ -24,7 +24,7 @@ SOFTWARE. <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="branch-cordova-sdk" - version="4.2.2"> + version="5.2.1"> <!-- Description --> <name>branch-cordova-sdk</name> @@ -63,7 +63,7 @@ SOFTWARE. <!-- Manifest configuration is done via a js script. We should move it to this config in the future. --> <source-file src="src/android/io/branch/BranchSDK.java" target-dir="src/io/branch" /> - <framework src="io.branch.sdk.android:library:5.0.7"/> + <framework src="io.branch.sdk.android:library:5.2.7"/> </platform> <!-- iOS --> @@ -84,10 +84,10 @@ SOFTWARE. <podspec> <config> - <source url="https://github.com/CocoaPods/Specs.git"/> + <source url="https://cdn.cocoapods.org/"/> </config> <pods> - <pod name="Branch" spec="~> 1.39.2" /> + <pod name="Branch" spec="~> 1.45.2" /> </pods> </podspec> </platform> diff --git a/src/__tests__/index.js b/src/__tests__/index.js index 8a843114..bd340080 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -66,18 +66,7 @@ exports.defineAutoTests = function() { expect(window.Branch.userCompletedAction).toBeDefined(); expect(_typeof(window.Branch.userCompletedAction)).toBe("function"); }); - it("should contain a method called loadRewards()", function() { - expect(window.Branch.loadRewards).toBeDefined(); - expect(_typeof(window.Branch.loadRewards)).toBe("function"); - }); - it("should contain a method called redeemRewards()", function() { - expect(window.Branch.redeemRewards).toBeDefined(); - expect(_typeof(window.Branch.redeemRewards)).toBe("function"); - }); - it("should contain a method called creditHistory()", function() { - expect(window.Branch.creditHistory).toBeDefined(); - expect(_typeof(window.Branch.creditHistory)).toBe("function"); - }); + }); describe("Branch.getLatestReferringParams()", function() { @@ -219,80 +208,4 @@ exports.defineAutoTests = function() { 10000 ); }); - - describe("Branch.loadRewards()", function() { - beforeEach(function(done) { - initSession().then(function() { - done(); - }); - }, 3000); - it( - "should return an object response", - function(done) { - window.Branch.loadRewards().then( - function(res) { - expect( - typeof res === "undefined" ? "undefined" : _typeof(res) - ).toBe("number"); - done(); - }, - function(err) { - expect( - typeof err === "undefined" ? "undefined" : _typeof(err) - ).toBe("string"); - done(); - } - ); - }, - 10000 - ); - }); - - describe("Branch.redeemRewards()", function() { - beforeEach(function(done) { - initSession().then(function() { - done(); - }); - }, 3000); - it( - "should return an object/string error response", - function(done) { - window.Branch.redeemRewards(100).then( - function(res) { - expect( - typeof res === "undefined" ? "undefined" : _typeof(res) - ).toBe("object"); - done(); - }, - function(err) { - expect( - typeof err === "undefined" ? "undefined" : _typeof(err) - ).toBe("string"); - done(); - } - ); - }, - 10000 - ); - }); - - describe("Branch.creditHistory()", function() { - beforeEach(function(done) { - initSession().then(function() { - done(); - }); - }, 3000); - it( - "should return the credit balance", - function(done) { - window.Branch.creditHistory().then(function(res) { - expect(typeof res === "undefined" ? "undefined" : _typeof(res)).toBe( - "object" - ); - done(); - }); - }, - 10000 - ); - }); }; diff --git a/src/android/io/branch/BranchSDK.java b/src/android/io/branch/BranchSDK.java index cc23d603..ea7b11a5 100644 --- a/src/android/io/branch/BranchSDK.java +++ b/src/android/io/branch/BranchSDK.java @@ -1,40 +1,39 @@ package io.branch; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Intent; -import android.util.Log; -import android.annotation.TargetApi; import android.net.Uri; import android.os.Build; +import android.util.Log; +import android.util.Base64; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Iterator; +import java.io.IOException; import io.branch.indexing.BranchUniversalObject; import io.branch.referral.Branch; -import io.branch.referral.PrefHelper; import io.branch.referral.BranchError; import io.branch.referral.BranchViewHandler; +import io.branch.referral.ServerRequestGetCPID.BranchCrossPlatformIdListener; +import io.branch.referral.ServerRequestGetLATD.BranchLastAttributedTouchDataListener; import io.branch.referral.SharingHelper; +import io.branch.referral.QRCode.BranchQRCode; import io.branch.referral.util.BRANCH_STANDARD_EVENT; +import io.branch.referral.util.BranchCPID; import io.branch.referral.util.BranchEvent; -import io.branch.referral.util.CommerceEvent; +import io.branch.referral.util.ContentMetadata; import io.branch.referral.util.CurrencyType; -import io.branch.referral.util.Product; -import io.branch.referral.util.ProductCategory; import io.branch.referral.util.ShareSheetStyle; -import io.branch.referral.ServerRequestGetLATD.BranchLastAttributedTouchDataListener; -import io.branch.referral.ServerRequestGetCPID.BranchCrossPlatformIdListener; -import io.branch.referral.util.BranchCPID; public class BranchSDK extends CordovaPlugin { @@ -124,7 +123,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo Runnable r = new RunnableThread(action, args, callbackContext); - if (action.equals("setDebug")) { + if (action.equals("enableLogging")) { cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("setCookieBasedMatching")) { @@ -146,21 +145,14 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } else if (action.equals("userCompletedAction")) { if (args.length() < 1 && args.length() > 2) { - callbackContext.error(String.format("Parameter mismatched. 1-2 is required but %d is given", args.length())); - return false; - } - cordova.getActivity().runOnUiThread(r); - return true; - } else if (action.equals("sendCommerceEvent")) { - if (args.length() < 1 && args.length() > 2) { - callbackContext.error(String.format("Parameter mismatched. 1-2 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("sendBranchEvent")) { if (args.length() < 1 && args.length() > 2) { - callbackContext.error(String.format("Parameter mismatched. 1-2 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); @@ -174,22 +166,9 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } else if (action.equals("logout")) { cordova.getActivity().runOnUiThread(r); return true; - } else if (action.equals("loadRewards")) { - cordova.getActivity().runOnUiThread(r); - return true; - } else if (action.equals("redeemRewards")) { - if (args.length() < 1 && args.length() > 2) { - callbackContext.error(String.format("Parameter mismatched. 1-2 is required but %d is given", args.length())); - return false; - } - cordova.getActivity().runOnUiThread(r); - return true; - } else if (action.equals("getCreditHistory")) { - cordova.getActivity().runOnUiThread(r); - return true; } else if (action.equals("createBranchUniversalObject")) { if (args.length() != 1) { - callbackContext.error(String.format("Parameter mismatched. 1 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); @@ -201,25 +180,25 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } else if (action.equals("lastAttributedTouchData")) { cordova.getActivity().runOnUiThread(r); - return true; + return true; } else if (action.equals(("generateShortUrl"))) { if (args.length() != 3) { - callbackContext.error(String.format("Parameter mismatched. 3 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("registerView")) { if (args.length() != 1) { - callbackContext.error(String.format("Parameter mismatched. 1 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("showShareSheet")) { if (args.length() < 3) { - callbackContext.error(String.format("Parameter mismatched. 3 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); @@ -252,6 +231,13 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo branchObjectWrappers.set(args.getInt(0), branchObjWrapper); + } else if (action.equals("getBranchQRCode")) { + if (args.length() != 4) { + callbackContext.error(String.format("Parameter count mismatch")); + return false; + } + cordova.getActivity().runOnUiThread(r); + return true; } return true; @@ -264,7 +250,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return false; } - + public void crossPlatformIds(CallbackContext callbackContext) { this.instance.getCrossPlatformIds(new BranchCPIDListener(callbackContext)); } @@ -335,64 +321,6 @@ private void logout(CallbackContext callbackContext) { } - /** - * <p>Redeems the specified number of credits from the "default" bucket, if there are sufficient - * credits within it. If the number to redeem exceeds the number available in the bucket, all of - * the available credits will be redeemed instead.</p> - * - * @param value An {@link Integer} specifying the number of credits to attempt to redeem from - * the bucket. - * @param callbackContext A callback to execute at the end of this method - */ - private void redeemRewards(final int value, CallbackContext callbackContext) { - - this.instance.redeemRewards(value, new RedeemRewardsListener(callbackContext)); - - } - - /** - * <p>Redeems the specified number of credits from the "default" bucket, if there are sufficient - * credits within it. If the number to redeem exceeds the number available in the bucket, all of - * the available credits will be redeemed instead.</p> - * - * @param value An {@link Integer} specifying the number of credits to attempt to redeem from - * the bucket. - * @param bucket The name of the bucket to remove the credits from. - * @param callbackContext A callback to execute at the end of this method - */ - private void redeemRewards(int value, String bucket, CallbackContext callbackContext) { - - this.instance.redeemRewards(bucket, value, new RedeemRewardsListener(callbackContext)); - - } - - /** - * <p>Retrieves rewards for the current session, with a callback to perform a predefined - * action following successful report of state change. You'll then need to call getCredits - * in the callback to update the credit totals in your UX.</p> - * - * @param callbackContext A callback to execute at the end of this method - * @param bucket Load reward of a specific bucket - */ - private void loadRewards(String bucket, CallbackContext callbackContext) { - - this.instance.loadRewards(new LoadRewardsListener(bucket, callbackContext, this.instance)); - - } - - /** - * <p>Retrieves rewards for the current session, with a callback to perform a predefined - * action following successful report of state change. You'll then need to call getCredits - * in the callback to update the credit totals in your UX.</p> - * - * @param callbackContext A callback to execute at the end of this method - */ - private void loadRewards(CallbackContext callbackContext) { - - this.instance.loadRewards(new LoadRewardsListener(callbackContext, this.instance)); - - } - /** * <p>Returns the parameters associated with the link that referred the session. If a user * clicks a link, and then opens the app, initSession will return the paramters of the link @@ -621,6 +549,70 @@ private void generateShortUrl(int instanceIdx, JSONObject options, JSONObject co } + /** + * Generate a QR code. + * + * @param qrCodeSettings A {@link JSONObject} value to set QR cide options. + * @param instanceIdx The instance index from branchObjects array + * @param options A {@link JSONObject} value to set URL options. + * @param controlParams A {@link JSONObject} value to set the URL control parameters. + */ + private void getBranchQRCode(JSONObject qrCodeSettings, int instanceIdx, JSONObject options, JSONObject controlParams, CallbackContext callbackContext) throws JSONException { + + BranchLinkProperties linkProperties = createLinkProperties(options, controlParams); + + BranchUniversalObjectWrapper branchUniversalWrapper = (BranchUniversalObjectWrapper) this.branchObjectWrappers.get(instanceIdx); + BranchUniversalObject buo = branchUniversalWrapper.branchUniversalObj; + + BranchQRCode branchQRCode = new BranchQRCode(); + if (qrCodeSettings.has("codeColor")) { + branchQRCode.setCodeColor(qrCodeSettings.getString("codeColor")); + } + if (qrCodeSettings.has("backgroundColor")) { + branchQRCode.setBackgroundColor(qrCodeSettings.getString("backgroundColor")); + } + if (qrCodeSettings.has("centerLogo")) { + branchQRCode.setCenterLogo(qrCodeSettings.getString("centerLogo")); + } + if (qrCodeSettings.has("width")) { + branchQRCode.setWidth(qrCodeSettings.getInt("width")); + } + if (qrCodeSettings.has("margin")) { + branchQRCode.setMargin(qrCodeSettings.getInt("margin")); + } + if (qrCodeSettings.has("imageFormat")) { + String imageFormat = qrCodeSettings.getString("imageFormat"); + if (imageFormat != null ) { + if (imageFormat.equals("JPEG")) { + branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.JPEG); + } else { + branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.PNG); + } + } + } + + try { + branchQRCode.getQRCodeAsData(this.activity, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { + @Override + public void onSuccess(byte[] qrCodeData) { + String qrCodeString = Base64.encodeToString(qrCodeData, Base64.DEFAULT); + Log.d(LCAT, qrCodeString); + callbackContext.success(qrCodeString); + } + + @Override + public void onFailure(Exception e) { + Log.d(LCAT, e.getMessage()); + callbackContext.error(e.getMessage()); + } + }); + } catch (IOException e) { + e.printStackTrace(); + Log.d(LCAT, e.getMessage()); + callbackContext.error(e.getMessage()); + } + } + /** * <p>Sets the cookie based matching for all incoming requests.</p> * <p>If you want cookie based matching, call this <b>before</b> initUserSession</p> @@ -640,15 +632,19 @@ private void setCookieBasedMatching(String linkDomain, CallbackContext callbackC } /** - * <p>Sets the library to function in debug mode, enabling logging of all requests.</p> - * <p>If you want to flag debug, call this <b>before</b> initUserSession</p> + * <p>Enabling Branch SDK logging</p> * - * @param isEnable A {@link Boolean} value to enable/disable debugging mode for the app. + * @param isEnable A {@link Boolean} value to enable/disable logging * @param callbackContext A callback to execute at the end of this method */ - private void setDebug(boolean isEnable, CallbackContext callbackContext) { + private void enableLogging(boolean isEnable, CallbackContext callbackContext) { this.activity = this.cordova.getActivity(); - Branch.enableLogging(); + if (isEnable == true) { + Branch.enableLogging(); + } else { + Branch.disableLogging(); + } + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, isEnable)); } @@ -728,91 +724,6 @@ private void userCompletedAction(String action, JSONObject metaData, CallbackCon } - /** - * <p>A void call to indicate that the user has performed a specific commerce event and for that to be - * reported to the Branch API.</p> - * - * @param action A {@link String} value to be passed as an commerce event that the user has - * carried out. - * @param metaData A {@link JSONObject} containing app-defined meta-data to be attached to a - * user action that has just been completed. - * @param callbackContext A callback to execute at the end of this method - */ - private void sendCommerceEvent(JSONObject action, JSONObject metaData, CallbackContext callbackContext) throws JSONException { - - CommerceEvent commerce = new CommerceEvent(); - Iterator<String> keys = action.keys(); - while (keys.hasNext()) { - String key = keys.next(); - String val; - try { - val = action.getString(key); - } catch (JSONException e) { - e.printStackTrace(); - callbackContext.error("Invalid key-value for " + key); - return; - } - if (key.equals("revenue")) { - commerce.setRevenue(Double.parseDouble(val)); - } else if (key.equals("currency")) { - commerce.setCurrencyType(CurrencyType.values()[Integer.parseInt(val)]); - } else if (key.equals("transactionID")) { - commerce.setTransactionID(val); - } else if (key.equals("coupon")) { - commerce.setCoupon(val); - } else if (key.equals("shipping")) { - commerce.setShipping(Double.parseDouble(val)); - } else if (key.equals("tax")) { - commerce.setTax(Double.parseDouble(val)); - } else if (key.equals("affiliation")) { - commerce.setAffiliation(val); - } else if (key.equals("products")) { - JSONArray products = new JSONArray(); - try { - products = action.getJSONArray(key); - } catch (JSONException e) { - e.printStackTrace(); - callbackContext.error("Invalid key-value for " + key); - return; - } - for (int i = 0; i < products.length(); ++i) { - Product product = new Product(); - JSONObject item = products.getJSONObject(i); - keys = item.keys(); - while (keys.hasNext()) { - key = keys.next(); - try { - val = item.getString(key); - } catch (JSONException e) { - e.printStackTrace(); - callbackContext.error("Invalid key-value for product for " + key); - return; - } - if (key.equals("sku")) { - product.setSku(val); - } else if (key.equals("name")) { - product.setName(val); - } else if (key.equals("price")) { - product.setPrice(Double.parseDouble(val)); - } else if (key.equals("quantity")) { - product.setQuantity(Integer.parseInt(val)); - } else if (key.equals("brand")) { - product.setBrand(val); - } else if (key.equals("category")) { - product.setCategory(ProductCategory.values()[Integer.parseInt(val)]); - } else if (key.equals("variant")) { - product.setVariant(val); - } - } - commerce.addProduct(product); - } - } - } - - this.instance.sendCommerceEvent(commerce, metaData, new BranchViewEventsListener(callbackContext)); - - } - public void sendBranchEvent(String eventName, CallbackContext callbackContext) throws JSONException { BranchEvent event; @@ -869,12 +780,19 @@ public void sendBranchEvent(String eventName, JSONObject metaData, CallbackConte event.setCustomerEventAlias(metaData.getString("customerEventAlias")); } else if (key.equals("customData")) { JSONObject customData = metaData.getJSONObject("customData"); - keys = customData.keys(); + Iterator<String> customDataKeys = customData.keys(); - while (keys.hasNext()) { - String keyValue = (String) keys.next(); + while (customDataKeys.hasNext()) { + String keyValue = (String) customDataKeys.next(); event.addCustomDataProperty(keyValue, customData.getString(keyValue)); } + } else if (key.equals("contentMetadata")) { + JSONArray contentMetadata = metaData.getJSONArray("contentMetadata"); + for (int i = 0; i < contentMetadata.length(); i++) { + JSONObject item = contentMetadata.getJSONObject(i); + BranchUniversalObject universalObject = this.getContentItem(item); + event.addContentItems(universalObject); + } } } @@ -882,16 +800,45 @@ public void sendBranchEvent(String eventName, JSONObject metaData, CallbackConte //callbackContext.success(); } - /** - * <p>Gets the credit history of the specified bucket and triggers a callback to handle the - * response.</p> - * - * @param callbackContext A callback to execute at the end of this method - */ - private void getCreditHistory(CallbackContext callbackContext) { + private BranchUniversalObject getContentItem(JSONObject item) throws JSONException { + BranchUniversalObject universalObject = new BranchUniversalObject(); + ContentMetadata contentMetadata = new ContentMetadata(); + Iterator<String> keys = item.keys(); + while (keys.hasNext()) { + String key = keys.next(); - this.instance.getCreditHistory(new CreditHistoryListener(callbackContext)); + switch (key) { + case "quantity": + contentMetadata.setQuantity(Double.parseDouble(item.getString(key))); + break; + case "price": + contentMetadata.price = Double.parseDouble(item.getString(key)); + break; + case "currency": + String currencyString = item.getString(key); + CurrencyType currency = CurrencyType.getValue(currencyString); + if (currency != null) { + contentMetadata.currencyType = currency; + } + break; + case "productName": + contentMetadata.setProductName(item.getString(key)); + break; + case "productBrand": + contentMetadata.setProductBrand(item.getString(key)); + break; + case "sku": + contentMetadata.setSku(item.getString(key)); + break; + default: + contentMetadata.addCustomMetadata(key, item.getString(key)); + break; + } + } + + universalObject.setContentMetadata(contentMetadata); + return universalObject; } /** @@ -1127,78 +1074,6 @@ public void onRegisterViewFinished(boolean registered, BranchError error) { } } - protected class LoadRewardsListener implements Branch.BranchReferralStateChangedListener { - private CallbackContext _callbackContext; - private Branch _instance; - private String _bucket; - - public LoadRewardsListener(String bucket, CallbackContext callbackContext, Branch instance) { - this._callbackContext = callbackContext; - this._instance = instance; - this._bucket = bucket; - } - - public LoadRewardsListener(CallbackContext callbackContext, Branch instance) { - this._callbackContext = callbackContext; - this._instance = instance; - this._bucket = ""; - } - - // Listener that implements BranchReferralStateChangedListener for loadRewards - @Override - public void onStateChanged(boolean changed, BranchError error) { - if (error == null) { - - int credits = 0; - - if (this._bucket.length() > 0) { - credits = this._instance.getCreditsForBucket(this._bucket); - } else { - credits = this._instance.getCredits(); - } - - this._callbackContext.success(credits); - - } else { - - String errorMessage = error.getMessage(); - - Log.d(LCAT, errorMessage); - - this._callbackContext.error(errorMessage); - - } - - } - } - - protected class RedeemRewardsListener implements Branch.BranchReferralStateChangedListener { - private CallbackContext _callbackContext; - - // Constructor that takes in a required callbackContext object - public RedeemRewardsListener(CallbackContext callbackContext) { - this._callbackContext = callbackContext; - } - - // Listener that implements BranchReferralStateChangedListener for redeemRewards - @Override - public void onStateChanged(boolean changed, BranchError error) { - - if (error == null) { - - this._callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, /* send boolean: is changed */ changed)); - - } else { - - String errorMessage = error.getMessage(); - - Log.d(LCAT, errorMessage); - - this._callbackContext.error(errorMessage); - } - } - } - protected class GenerateShortUrlListener implements Branch.BranchLinkCreateListener { private CallbackContext _callbackContext; @@ -1372,64 +1247,6 @@ public void onChannelSelected(String channelName) { } } - protected class CreditHistoryListener implements Branch.BranchListResponseListener { - private CallbackContext _callbackContext; - - // Constructor that takes in a required callbackContext object - public CreditHistoryListener(CallbackContext callbackContext) { - this._callbackContext = callbackContext; - } - - // Listener that implements BranchListResponseListener for getCreditHistory() - @Override - public void onReceivingResponse(JSONArray list, BranchError error) { - - ArrayList<String> errors = new ArrayList<String>(); - - if (error == null) { - - JSONArray data = new JSONArray(); - - if (list != null) { - - for (int i = 0, limit = list.length(); i < limit; ++i) { - - JSONObject entry; - - try { - entry = list.getJSONObject(i); - data.put(entry); - } catch (JSONException e) { - e.printStackTrace(); - errors.add(e.getMessage()); - } - - } - - } - - if (errors.size() > 0) { - StringBuilder sb = new StringBuilder(); - for (String s : errors) { - sb.append(s); - sb.append("\n"); - } - this._callbackContext.error(sb.toString()); - } else { - this._callbackContext.success(data); - } - } else { - - String errorMessage = error.getMessage(); - - Log.d(LCAT, errorMessage); - - this._callbackContext.error(errorMessage); - - } - } - } - protected class RunnableThread implements Runnable { private String action; @@ -1446,8 +1263,8 @@ public void run() { try { Log.d(LCAT, "Runnable: " + this.action); - if (this.action.equals("setDebug")) { - setDebug(this.args.getBoolean(0), this.callbackContext); + if (this.action.equals("enableLogging")) { + enableLogging(this.args.getBoolean(0), this.callbackContext); } else if (this.action.equals("setCookieBasedMatching")) { setCookieBasedMatching(this.args.getString(0), this.callbackContext); } else if (this.action.equals("disableTracking")) { @@ -1466,8 +1283,6 @@ public void run() { } else if (this.args.length() == 1) { userCompletedAction(this.args.getString(0), this.callbackContext); } - } else if (this.action.equals("sendCommerceEvent")) { - sendCommerceEvent(this.args.getJSONObject(0), this.args.getJSONObject(1), this.callbackContext); } else if (this.action.equals("sendBranchEvent")) { if (this.args.length() == 2) { sendBranchEvent(this.args.getString(0), this.args.getJSONObject(1), this.callbackContext); @@ -1480,20 +1295,6 @@ public void run() { getLatestReferringParams(this.callbackContext); } else if (this.action.equals("logout")) { logout(this.callbackContext); - } else if (this.action.equals("loadRewards")) { - if (this.args.length() == 1) { - loadRewards(this.args.getString(0), this.callbackContext); - } else { - loadRewards(this.callbackContext); - } - } else if (this.action.equals("redeemRewards")) { - if (this.args.length() == 1) { - redeemRewards(this.args.getInt(0), this.callbackContext); - } else if (this.args.length() == 2) { - redeemRewards(this.args.getInt(0), this.args.getString(1), this.callbackContext); - } - } else if (this.action.equals("getCreditHistory")) { - getCreditHistory(this.callbackContext); } else if (this.action.equals("createBranchUniversalObject")) { createBranchUniversalObject(this.args.getJSONObject(0), this.callbackContext); } else if (this.action.equals("crossPlatformIds")) { @@ -1502,6 +1303,8 @@ public void run() { lastAttributedTouchData(this.callbackContext); } else if (this.action.equals(("generateShortUrl"))) { generateShortUrl(this.args.getInt(0), this.args.getJSONObject(1), this.args.getJSONObject(2), this.callbackContext); + } else if (this.action.equals(("getBranchQRCode"))) { + getBranchQRCode(this.args.getJSONObject(0), this.args.getInt(1), this.args.getJSONObject(2), this.args.getJSONObject(3), this.callbackContext); } else if (this.action.equals("registerView")) { registerView(this.args.getInt(0), this.callbackContext); } else if (this.action.equals("showShareSheet")) { diff --git a/src/index.js b/src/index.js index c0195306..de2b843e 100644 --- a/src/index.js +++ b/src/index.js @@ -15,26 +15,31 @@ const standardEvent = { STANDARD_EVENT_INITIATE_PURCHASE: "INITIATE_PURCHASE", STANDARD_EVENT_ADD_PAYMENT_INFO: "ADD_PAYMENT_INFO", STANDARD_EVENT_PURCHASE: "PURCHASE", - STANDARD_EVENT_SPEND_CREDITS: "SPEND_CREDITS", STANDARD_EVENT_SEARCH: "SEARCH", STANDARD_EVENT_VIEW_ITEM: "VIEW_ITEM", STANDARD_EVENT_VIEW_ITEMS: "VIEW_ITEMS", STANDARD_EVENT_RATE: "RATE", STANDARD_EVENT_SHARE: "SHARE", + STANDARD_EVENT_INITIATE_STREAM: "INITIATE_STREAM", + STANDARD_EVENT_COMPLETE_STREAM: "COMPLETE_STREAM", STANDARD_EVENT_COMPLETE_REGISTRATION: "COMPLETE_REGISTRATION", STANDARD_EVENT_COMPLETE_TUTORIAL: "COMPLETE_TUTORIAL", STANDARD_EVENT_ACHIEVE_LEVEL: "ACHIEVE_LEVEL", - STANDARD_EVENT_UNLOCK_ACHIEVEMENT: "UNLOCK_ACHIEVEMENT" + STANDARD_EVENT_UNLOCK_ACHIEVEMENT: "UNLOCK_ACHIEVEMENT", + STANDARD_EVENT_INVITE: "INVITE", + STANDARD_EVENT_LOGIN: "LOGIN", + STANDARD_EVENT_SUBSCRIBE: "SUBSCRIBE", + STANDARD_EVENT_START_TRIAL: "START_TRIAL" } // Branch prototype var Branch = function Branch() { - this.debugMode = false; + this.enableLogging = false; this.trackingDisabled = false; this.sessionInitialized = false; }; -// JavsSript to SDK wrappers +// JavaScript to SDK wrappers function execute(method, params) { var output = !params ? [] : params; @@ -116,11 +121,19 @@ Branch.prototype.setRequestMetadata = function setRequestMetadata(key, val) { return execute("setRequestMetadata", [key, val]); }; +// Deprecated. Replaced by setLogging(isEnabled) and test devices. https://help.branch.io/using-branch/docs/adding-test-devices Branch.prototype.setDebug = function setDebug(isEnabled) { + return new Promise(function promise(resolve, reject) { + resolve(false); + }); +}; + +// For early lifecycle logging, we recommend you enable logging in the native iOS or Android code. +Branch.prototype.setLogging = function setLogging(isEnabled) { var value = typeof isEnabled !== "boolean" ? false : isEnabled; - this.debugMode = value; + this.enableLogging = value; - return execute("setDebug", [value]); + return execute("enableLogging", [value]); }; Branch.prototype.setCookieBasedMatching = function setCookieBasedMatching( @@ -301,24 +314,6 @@ Branch.prototype.createBranchUniversalObject = function createBranchUniversalObj }); }; -Branch.prototype.loadRewards = function loadRewards(bucket) { - var output = !bucket ? "" : bucket; - return execute("loadRewards", [output]); -}; - -Branch.prototype.redeemRewards = function redeemRewards(value, bucket) { - var params = [value]; - if (bucket) { - params.push(bucket); - } - - return execute("redeemRewards", params); -}; - -Branch.prototype.creditHistory = function creditHistory() { - return execute("getCreditHistory"); -}; - Branch.prototype.crossPlatformIds = function crossPlatformIds() { return execute("crossPlatformIds"); }; @@ -327,5 +322,29 @@ Branch.prototype.lastAttributedTouchData = function lastAttributedTouchData() { return execute("lastAttributedTouchData"); }; +Branch.prototype.getBranchQRCode = function getBranchQRCode( + qrCodeSettings, + branchUniversalObject, + analytics, + properties +) { + var args = []; + if (qrCodeSettings) { + args.push(qrCodeSettings); + } + if (branchUniversalObject) { + args.push(branchUniversalObject.instanceId); + } + if (analytics) { + args.push(analytics); + } + if (properties) { + args.push(properties); + } + + return execute("getBranchQRCode", args); +}; + + // export Branch object module.exports = new Branch(); diff --git a/src/ios/BranchSDK.h b/src/ios/BranchSDK.h index d1ca2c9c..bc52b107 100644 --- a/src/ios/BranchSDK.h +++ b/src/ios/BranchSDK.h @@ -29,7 +29,7 @@ - (void)enableTestMode:(CDVInvokedUrlCommand*)command; - (void)initSession:(CDVInvokedUrlCommand*)command; - (void)disableTracking:(CDVInvokedUrlCommand*)command; -- (void)setDebug:(CDVInvokedUrlCommand*)command; +- (void)enableLogging:(CDVInvokedUrlCommand*)command; - (void)getAutoInstance:(CDVInvokedUrlCommand*)command; - (void)getLatestReferringParams:(CDVInvokedUrlCommand*)command; - (void)getFirstReferringParams:(CDVInvokedUrlCommand*)command; @@ -39,11 +39,6 @@ - (void)logout:(CDVInvokedUrlCommand*)command; - (void)delayInitToCheckForSearchAds:(CDVInvokedUrlCommand*)command; -// Branch Referral Reward System -- (void)loadRewards:(CDVInvokedUrlCommand*)command; -- (void)redeemRewards:(CDVInvokedUrlCommand*)command; -- (void)getCreditHistory:(CDVInvokedUrlCommand*)command; - // Branch Universal Object Methods - (void)createBranchUniversalObject:(CDVInvokedUrlCommand*)command; - (void)registerView:(CDVInvokedUrlCommand*)command; diff --git a/src/ios/BranchSDK.m b/src/ios/BranchSDK.m index aca46bc7..c5cd2d50 100644 --- a/src/ios/BranchSDK.m +++ b/src/ios/BranchSDK.m @@ -157,14 +157,14 @@ - (void)disableTracking:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -- (void)setDebug:(CDVInvokedUrlCommand*)command +- (void)enableLogging:(CDVInvokedUrlCommand*)command { - bool enableDebug = [[command.arguments objectAtIndex:0] boolValue]; - if (enableDebug) { - [[Branch getInstance] setDebug]; + bool enableLogging = [[command.arguments objectAtIndex:0] boolValue]; + if (enableLogging) { + [[Branch getInstance] enableLogging]; } - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:enableDebug]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:enableLogging]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @@ -305,95 +305,19 @@ -(void)sendBranchEvent:(CDVInvokedUrlCommand*)command else if ([key isEqualToString:@"customData"] && [[metadata objectForKey:key] isKindOfClass:[NSMutableDictionary class]]) { event.customData = [metadata objectForKey:key]; } + else if ([key isEqualToString:@"contentMetadata"]){ + NSMutableArray *mArray = [[NSMutableArray alloc]init]; + + for (NSDictionary *dataDictionary in [metadata objectForKey:key]){ + BranchUniversalObject *contentItem = [BranchUniversalObject objectWithDictionary:(dataDictionary)]; + [mArray addObject:contentItem]; + } + event.contentItems = [mArray copy]; + } } [event logEvent]; } -- (void)sendCommerceEvent:(CDVInvokedUrlCommand*)command -{ - NSDictionary *data = [command.arguments objectAtIndex:0]; - NSDictionary *metadata; - BNCCommerceEvent *commerce = [[BNCCommerceEvent alloc] init]; - NSArray *categories = [NSArray arrayWithObjects:BNCProductCategoryAnimalSupplies, BNCProductCategoryApparel, BNCProductCategoryArtsEntertainment, BNCProductCategoryBabyToddler, BNCProductCategoryBusinessIndustrial, BNCProductCategoryCamerasOptics, BNCProductCategoryElectronics, BNCProductCategoryFoodBeverageTobacco, BNCProductCategoryFurniture, BNCProductCategoryHardware, BNCProductCategoryHealthBeauty, BNCProductCategoryHomeGarden, BNCProductCategoryLuggageBags, BNCProductCategoryMature, BNCProductCategoryMedia, BNCProductCategoryOfficeSupplies, BNCProductCategoryReligious, BNCProductCategorySoftware, BNCProductCategorySportingGoods, BNCProductCategoryToysGames, BNCProductCategoryVehiclesParts, nil]; - NSArray *currencies = [NSArray arrayWithObjects:@"AED", @"AFN", @"ALL", @"AMD", @"ANG", @"AOA", @"ARS", @"AUD", @"AWG", @"AZN", @"BAM", @"BBD", @"BDT", @"BGN", @"BHD", @"BIF", @"BMD", @"BND", @"BOB", @"BOV", @"BRL", @"BSD", @"BTN", @"BWP", @"BYN", @"BYR", @"BZD", @"CAD", @"CDF", @"CHE", @"CHF", @"CHW", @"CLF", @"CLP", @"CNY", @"COP", @"COU", @"CRC", @"CUC", @"CUP", @"CVE", @"CZK", @"DJF", @"DKK", @"DOP", @"DZD", @"EGP", @"ERN", @"ETB", @"EUR", @"FJD", @"FKP", @"GBP", @"GEL", @"GHS", @"GIP", @"GMD", @"GNF", @"GTQ", @"GYD", @"HKD", @"HNL", @"HRK", @"HTG", @"HUF", @"IDR", @"ILS", @"INR", @"IQD", @"IRR", @"ISK", @"JMD", @"JOD", @"JPY", @"KES", @"KGS", @"KHR", @"KMF", @"KPW", @"KRW", @"KWD", @"KYD", @"KZT", @"LAK", @"LBP", @"LKR", @"LRD", @"LSL", @"LYD", @"MAD", @"MDL", @"MGA", @"MKD", @"MMK", @"MNT", @"MOP", @"MRO", @"MUR", @"MVR", @"MWK", @"MXN", @"MXV", @"MYR", @"MZN", @"NAD", @"NGN", @"NIO", @"NOK", @"NPR", @"NZD", @"OMR", @"PAB", @"PEN", @"PGK", @"PHP", @"PKR", @"PLN", @"PYG", @"QAR", @"RON", @"RSD", @"RUB", @"RWF", @"SAR", @"SBD", @"SCR", @"SDG", @"SEK", @"SGD", @"SHP", @"SLL", @"SOS", @"SRD", @"SSP", @"STD", @"SYP", @"SZL", @"THB", @"TJS", @"TMT", @"TND", @"TOP", @"TRY", @"TTD", @"TWD", @"TZS", @"UAH", @"UGX", @"USD", @"USN", @"UYI", @"UYU", @"UZS", @"VEF", @"VND", @"VUV", @"WST", @"XAF", @"XAG", @"XAU", @"XBA", @"XBB", @"XBC", @"XBD", @"XCD", @"XDR", @"XFU", @"XOF", @"XPD", @"XPF", @"XPT", @"XSU", @"XTS", @"XUA", @"XXX", @"YER", @"ZAR", @"ZMW", nil]; - - if ([command.arguments count] == 2) { - metadata = [command.arguments objectAtIndex:1]; - } - - for (id key in data) { - if ([key isEqualToString:@"revenue"]) { - NSString *value = ([[data objectForKey:key] isKindOfClass:[NSString class]]) ? [data objectForKey:key] : [[data objectForKey:key] stringValue]; - commerce.revenue = [NSDecimalNumber decimalNumberWithString:value]; - } - else if ([key isEqualToString:@"currency"]) { - NSString *value = ([[data objectForKey:key] isKindOfClass:[NSString class]]) ? [data objectForKey:key] : [[data objectForKey:key] stringValue]; - commerce.currency = [currencies objectAtIndex:[value intValue]]; - } - else if ([key isEqualToString:@"transactionID"]) { - commerce.transactionID = [data objectForKey:key]; - } - else if ([key isEqualToString:@"shipping"]) { - NSString *value = ([[data objectForKey:key] isKindOfClass:[NSString class]]) ? [data objectForKey:key] : [[data objectForKey:key] stringValue]; - commerce.shipping = [NSDecimalNumber decimalNumberWithString:value]; - } - else if ([key isEqualToString:@"tax"]) { - NSString *value = ([[data objectForKey:key] isKindOfClass:[NSString class]]) ? [data objectForKey:key] : [[data objectForKey:key] stringValue]; - commerce.tax = [NSDecimalNumber decimalNumberWithString:value]; - } - else if ([key isEqualToString:@"coupon"]) { - commerce.coupon = [data objectForKey:key]; - } - else if ([key isEqualToString:@"affiliation"]) { - commerce.affiliation = [data objectForKey:key]; - } - else if ([key isEqualToString:@"products"] && [[data objectForKey:key] isKindOfClass:[NSArray class]]) { - NSMutableArray *products = [[NSMutableArray alloc] init]; - NSArray *dataProducts = [data objectForKey:key]; - for (NSDictionary *dataDictionary in dataProducts) { - BNCProduct *product = [[BNCProduct alloc] init]; - for (id key in dataDictionary) { - if ([key isEqualToString:@"sku"]) { - product.sku = [dataDictionary objectForKey:key]; - } - else if ([key isEqualToString:@"name"]) { - product.name = [dataDictionary objectForKey:key]; - } - else if ([key isEqualToString:@"price"]) { - NSString *value = ([[dataDictionary objectForKey:key] isKindOfClass:[NSString class]]) ? [dataDictionary objectForKey:key] : [[dataDictionary objectForKey:key] stringValue]; - product.price = [NSDecimalNumber decimalNumberWithString:value]; - } - else if ([key isEqualToString:@"quantity"]) { - NSString *value = ([[dataDictionary objectForKey:key] isKindOfClass:[NSString class]]) ? [dataDictionary objectForKey:key] : [[dataDictionary objectForKey:key] stringValue]; - product.quantity = [NSNumber numberWithInt:[value intValue]]; - } - else if ([key isEqualToString:@"brand"]) { - product.brand = [dataDictionary objectForKey:key]; - } - else if ([key isEqualToString:@"category"]) { - NSString *value = ([[dataDictionary objectForKey:key] isKindOfClass:[NSString class]]) ? [dataDictionary objectForKey:key] : [[dataDictionary objectForKey:key] stringValue]; - product.category = [categories objectAtIndex:[value intValue]]; - } - else if ([key isEqualToString:@"variant"]) { - product.variant = [dataDictionary objectForKey:key]; - } - } - [products addObject:product]; - } - commerce.products = products; - } - } - - [[Branch getInstance] sendCommerceEvent:commerce metadata:metadata withCompletion:^(NSDictionary *response, NSError *error) { - CDVPluginResult *pluginResult = nil; - if (!error) { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: @"Success"]; - } else { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; - } - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; -} - (void)logout:(CDVInvokedUrlCommand*)command { @@ -422,92 +346,6 @@ - (void)delayInitToCheckForSearchAds:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -#pragma mark - Branch Referral Reward System - -- (void)loadRewards:(CDVInvokedUrlCommand*)command -{ - Branch *branch = [self getInstance]; - NSString *bucket = @""; - - if ([command.arguments count] == 1) { - bucket = [command.arguments objectAtIndex:0]; - } - - [branch loadRewardsWithCallback:^(BOOL changed, NSError *error) { - - CDVPluginResult* pluginResult = nil; - if(!error) { - int credits = 0; - - if ([bucket length]) { - credits = (int)[branch getCreditsForBucket:bucket]; - } else { - credits = (int)[branch getCredits]; - } - - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:credits]; - } - else { - NSLog(@"Load Rewards Error: %@", [error localizedDescription]); - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; - } - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; -} - -- (void)redeemRewards:(CDVInvokedUrlCommand*)command -{ - - NSInteger amount = ((NSNumber *)[command.arguments objectAtIndex:0]).integerValue; - Branch *branch = [self getInstance]; - - if ([command.arguments count] == 2) { - - NSString *bucket = [command.arguments objectAtIndex:1]; - - [branch redeemRewards:(NSInteger)amount forBucket:(NSString *)bucket callback:^(BOOL changed, NSError *error) { - CDVPluginResult* pluginResult = nil; - if (!error) { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:changed]; - } - else { - NSLog(@"Redeem Rewards Error: %@", [error localizedDescription]); - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; - } - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; - } else { - [branch redeemRewards:amount callback:^(BOOL changed, NSError *error) { - CDVPluginResult* pluginResult = nil; - if (!error) { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:changed]; - } - else { - NSLog(@"Redeem Rewards Error: %@", [error localizedDescription]); - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; - } - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; - } -} - -- (void)getCreditHistory:(CDVInvokedUrlCommand*)command -{ - Branch *branch = [self getInstance]; - - [branch getCreditHistoryWithCallback:^(NSArray *list, NSError *error) { - CDVPluginResult* pluginResult = nil; - if (!error) { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:list]; - } - else { - NSLog(@"Credit History Error: %@", [error localizedDescription]); - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; - } - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; -} - #pragma mark - Branch Universal Object Methods - (void)createBranchUniversalObject:(CDVInvokedUrlCommand*)command @@ -800,7 +638,7 @@ - (void)lastAttributedTouchData:(CDVInvokedUrlCommand *)command { NSMutableDictionary *json = [NSMutableDictionary new]; Branch *branch = [self getInstance]; - [branch lastAttributedTouchDataWithAttributionWindow:30 completion:^(BranchLastAttributedTouchData * _Nullable latd) { + [branch lastAttributedTouchDataWithAttributionWindow:30 completion:^(BranchLastAttributedTouchData * _Nullable latd, NSError * _Nullable error) { CDVPluginResult* pluginResult = nil; if (latd) { [json setObject:latd.attributionWindow forKey:@"attribution_window"]; @@ -814,6 +652,129 @@ - (void)lastAttributedTouchData:(CDVInvokedUrlCommand *)command { }]; } +- (void)getBranchQRCode:(CDVInvokedUrlCommand*)command +{ + int branchUniversalObjectId = [[command.arguments objectAtIndex:1] intValue]; + NSMutableDictionary *branchUniversalObjDict = [self.branchUniversalObjArray objectAtIndex:branchUniversalObjectId]; + BranchUniversalObject *branchUniversalObj = [branchUniversalObjDict objectForKey:@"branchUniversalObj"]; + + BranchLinkProperties *linkProperties = [BranchLinkProperties new]; + + NSDictionary *arg1 = [command.arguments objectAtIndex:2]; + NSDictionary *arg2 = [command.arguments objectAtIndex:3]; + + for (id key in arg1) { + if ([key isEqualToString:@"duration"]) { + linkProperties.matchDuration = (NSUInteger)[((NSNumber *)[arg1 objectForKey:key]) integerValue]; + } + else if ([key isEqualToString:@"feature"]) { + linkProperties.feature = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"stage"]) { + linkProperties.stage = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"campaign"]) { + linkProperties.campaign = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"alias"]) { + linkProperties.alias = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"channel"]) { + linkProperties.channel = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"tags"] && [[arg1 objectForKey:key] isKindOfClass:[NSArray class]]) { + linkProperties.tags = [arg1 objectForKey:key]; + } + } + if (arg2) { + for (id key in arg2) { + [linkProperties addControlParam:key withValue:[arg2 objectForKey:key]]; + } + } + + NSMutableDictionary *qrCodeSettingsMap = [command.arguments objectAtIndex:0]; + + BranchQRCode *qrCode = [BranchQRCode new]; + + if (qrCodeSettingsMap[@"codeColor"]) { + qrCode.codeColor = [self colorWithHexString:qrCodeSettingsMap[@"codeColor"]]; + } + if (qrCodeSettingsMap[@"backgroundColor"]) { + qrCode.backgroundColor = [self colorWithHexString:qrCodeSettingsMap[@"backgroundColor"]]; + } + if (qrCodeSettingsMap[@"centerLogo"]) { + qrCode.centerLogo = qrCodeSettingsMap[@"centerLogo"]; + } + if (qrCodeSettingsMap[@"width"]) { + qrCode.width = qrCodeSettingsMap[@"width"]; + } + if (qrCodeSettingsMap[@"margin"]) { + qrCode.margin = qrCodeSettingsMap[@"margin"]; + } + if (qrCodeSettingsMap[@"imageFormat"]) { + if ([qrCodeSettingsMap[@"imageFormat"] isEqual:@"JPEG"]) { + qrCode.imageFormat = BranchQRCodeImageFormatJPEG; + } else { + qrCode.imageFormat = BranchQRCodeImageFormatPNG; + } + } + + [qrCode getQRCodeAsData:branchUniversalObj linkProperties:linkProperties completion:^(NSData * _Nonnull qrCodeData, NSError * _Nonnull error) { + CDVPluginResult* pluginResult = nil; + + if (!error) { + NSString* imageString = [qrCodeData base64EncodedStringWithOptions:nil]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:imageString]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} + +- (UIColor *) colorWithHexString: (NSString *) hexString { + NSString *colorString = [[hexString stringByReplacingOccurrencesOfString: @"#" withString: @""] uppercaseString]; + CGFloat alpha, red, blue, green; + switch ([colorString length]) { + case 3: // #RGB + alpha = 1.0f; + red = [self colorComponentFrom: colorString start: 0 length: 1]; + green = [self colorComponentFrom: colorString start: 1 length: 1]; + blue = [self colorComponentFrom: colorString start: 2 length: 1]; + break; + case 4: // #ARGB + alpha = [self colorComponentFrom: colorString start: 0 length: 1]; + red = [self colorComponentFrom: colorString start: 1 length: 1]; + green = [self colorComponentFrom: colorString start: 2 length: 1]; + blue = [self colorComponentFrom: colorString start: 3 length: 1]; + break; + case 6: // #RRGGBB + alpha = 1.0f; + red = [self colorComponentFrom: colorString start: 0 length: 2]; + green = [self colorComponentFrom: colorString start: 2 length: 2]; + blue = [self colorComponentFrom: colorString start: 4 length: 2]; + break; + case 8: // #AARRGGBB + alpha = [self colorComponentFrom: colorString start: 0 length: 2]; + red = [self colorComponentFrom: colorString start: 2 length: 2]; + green = [self colorComponentFrom: colorString start: 4 length: 2]; + blue = [self colorComponentFrom: colorString start: 6 length: 2]; + break; + default: + NSLog(@"Error: Invalid color value. It should be a hex value of the form #RBG, #ARGB, #RRGGBB, or #AARRGGBB"); + break; + } + return [UIColor colorWithRed: red green: green blue: blue alpha: alpha]; +} + +- (CGFloat) colorComponentFrom: (NSString *) string start: (NSUInteger) start length: (NSUInteger) length { + NSString *substring = [string substringWithRange: NSMakeRange(start, length)]; + NSString *fullHex = length == 2 ? substring : [NSString stringWithFormat: @"%@%@", substring, substring]; + unsigned hexComponent; + [[NSScanner scannerWithString: fullHex] scanHexInt: &hexComponent]; + return hexComponent / 255.0; +} #pragma mark - URL Methods (not fully implemented YET!) diff --git a/src/scripts/__tests__/associatedDomains.test.js b/src/scripts/__tests__/associatedDomains.test.js new file mode 100644 index 00000000..6dd30779 --- /dev/null +++ b/src/scripts/__tests__/associatedDomains.test.js @@ -0,0 +1,10 @@ +const iosEntitlements = require('../ios/updateAssociatedDomains'); +const androidManifest = require('../android/updateAndroidManifest'); + +const preferences = { + iosLinkDomain: [], + androidLinkDomain: [], + linkDomain: ['cordova.app.link', 'cordova-alternate.app.link', 'test.app.link'] +} +console.log(iosEntitlements.updateAssociatedDomains(preferences)); +console.log(androidManifest.getAppLinkIntentFilterData(preferences)); diff --git a/src/scripts/android/updateAndroidManifest.js b/src/scripts/android/updateAndroidManifest.js index e14722cd..f2da6f76 100644 --- a/src/scripts/android/updateAndroidManifest.js +++ b/src/scripts/android/updateAndroidManifest.js @@ -6,7 +6,8 @@ // entry module.exports = { - writePreferences: writePreferences + writePreferences: writePreferences, + getAppLinkIntentFilterData: getAppLinkIntentFilterData }; // injects config.xml preferences into AndroidManifest.xml file. @@ -260,6 +261,10 @@ // app.link link domains need -alternate associated domains as well (for Deep Views) if (linkDomain.indexOf("app.link") !== -1) { + const isAlternateDomain = linkDomain.indexOf("-alternate") !== -1; + if(isAlternateDomain){ + continue; + } const first = linkDomain.split(".")[0]; const rest = linkDomain .split(".") diff --git a/src/scripts/examples/templates/cordova1/index.html b/src/scripts/examples/templates/cordova1/index.html index bea1c0c6..f37f794e 100644 --- a/src/scripts/examples/templates/cordova1/index.html +++ b/src/scripts/examples/templates/cordova1/index.html @@ -52,17 +52,6 @@ <button id="branchCommerce">Commerce</button> </section> </fieldset> - <fieldset> - <legend>Branch Referral</legend> - <section> - <button id="branchReferralsReward">Reward</button> - <button id="branchReferralsLoad">Load</button> - </section> - <section> - <button id="branchReferralsRedeem">Redeem</button> - <button id="branchReferralsHistory">History</button> - </section> - </fieldset> <script type="text/javascript" src="cordova.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> diff --git a/src/scripts/examples/templates/cordova1/index.js b/src/scripts/examples/templates/cordova1/index.js index da873783..74b3e9d4 100644 --- a/src/scripts/examples/templates/cordova1/index.js +++ b/src/scripts/examples/templates/cordova1/index.js @@ -29,10 +29,6 @@ var branchUser = document.getElementById("branchUser"); var branchLogout = document.getElementById("branchLogout"); var branchEvent = document.getElementById("branchEvent"); var branchCommerce = document.getElementById("branchCommerce"); -var branchReferralsReward = document.getElementById("branchReferralsReward"); -var branchReferralsLoad = document.getElementById("branchReferralsLoad"); -var branchReferralsRedeem = document.getElementById("branchReferralsRedeem"); -var branchReferralsHistory = document.getElementById("branchReferralsHistory"); // handle DOM branchUniversalObject.addEventListener("click", BranchUniversalObject); @@ -46,10 +42,6 @@ branchUser.addEventListener("click", BranchUser); branchLogout.addEventListener("click", BranchLogout); branchEvent.addEventListener("click", BranchEvent); branchCommerce.addEventListener("click", BranchCommerce); -branchReferralsReward.addEventListener("click", BranchReferralsReward); -branchReferralsLoad.addEventListener("click", BranchReferralsLoad); -branchReferralsRedeem.addEventListener("click", BranchReferralsRedeem); -branchReferralsHistory.addEventListener("click", BranchReferralsHistory); // run app.initialize(); @@ -353,44 +345,3 @@ function BranchSpotlight() { logger(err, true); }); } - -function BranchReferralsReward() { - Branch.userCompletedAction("add5credits") - .then(function success(res) { - logger(res); - }) - .catch(function error(err) { - logger(err, true); - }); -} - -function BranchReferralsLoad() { - Branch.loadRewards() - .then(function success(res) { - logger(res); - }) - .catch(function error(err) { - logger(err, true); - }); -} - -function BranchReferralsRedeem() { - var amount = 10; - Branch.redeemRewards(amount) - .then(function success(res) { - logger(res); - }) - .catch(function error(err) { - logger(err, true); - }); -} - -function BranchReferralsHistory() { - Branch.creditHistory() - .then(function success(res) { - logger(res); - }) - .catch(function error(err) { - logger(err, true); - }); -} diff --git a/src/scripts/ios/updateAssociatedDomains.js b/src/scripts/ios/updateAssociatedDomains.js index 4d54712f..b26547e9 100644 --- a/src/scripts/ios/updateAssociatedDomains.js +++ b/src/scripts/ios/updateAssociatedDomains.js @@ -12,7 +12,8 @@ // entry module.exports = { - addAssociatedDomains: addAssociatedDomains + addAssociatedDomains: addAssociatedDomains, + updateAssociatedDomains: updateAssociatedDomains }; // updates the associated domains from the link-domain field of the app's config.xml @@ -127,6 +128,11 @@ for (let i = 0; i < linkDomains.length; i++) { const linkDomain = linkDomains[i]; + const isAlternateDomain = linkDomain.indexOf("-alternate") !== -1; + if(isAlternateDomain){ + continue; + } + // add link domain to associated domain domainList.push(prefix + linkDomain); diff --git a/src/scripts/npm/processConfigXml.js b/src/scripts/npm/processConfigXml.js index 0f208cd8..0c0eb6b8 100644 --- a/src/scripts/npm/processConfigXml.js +++ b/src/scripts/npm/processConfigXml.js @@ -104,8 +104,7 @@ let exists; try { - fs.existsSync(pathToBranchJson); - exists = true; + exists = fs.existsSync(pathToBranchJson); } catch(err) { exists = false; }