1 package io.branch.rnbranch; 2 3 import android.app.Activity; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.IntentFilter; 7 import android.content.BroadcastReceiver; 8 import android.net.Uri; 9 10 import androidx.annotation.Nullable; 11 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 12 import android.util.Log; 13 import android.os.Handler; 14 15 import com.facebook.react.bridge.*; 16 import com.facebook.react.bridge.Promise; 17 import com.facebook.react.modules.core.*; 18 import com.facebook.react.bridge.ReadableMap; 19 20 import io.branch.referral.*; 21 import io.branch.referral.Branch.BranchLinkCreateListener; 22 import io.branch.referral.BuildConfig; 23 import io.branch.referral.util.*; 24 import io.branch.referral.Branch; 25 import io.branch.indexing.*; 26 27 import org.json.*; 28 29 import java.text.ParseException; 30 import java.text.SimpleDateFormat; 31 import java.util.*; 32 33 import javax.annotation.Nonnull; 34 35 public class RNBranchModule extends ReactContextBaseJavaModule { 36 public static final String REACT_CLASS = "RNBranch"; 37 public static final String REACT_MODULE_NAME = "RNBranch"; 38 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT = "io.branch.rnbranch.RNBranchModule.onInitSessionFinished"; 39 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_BRANCH_UNIVERSAL_OBJECT = "branch_universal_object"; 40 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_LINK_PROPERTIES = "link_properties"; 41 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS = "params"; 42 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR = "error"; 43 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_URI = "uri"; 44 public static final String NATIVE_INIT_SESSION_STARTED_EVENT = "io.branch.rnbranch.RNBranchModule.onInitSessionStarted"; 45 public static final String NATIVE_INIT_SESSION_STARTED_EVENT_URI = "uri"; 46 private static final String RN_INIT_SESSION_SUCCESS_EVENT = "RNBranch.initSessionSuccess"; 47 private static final String RN_INIT_SESSION_ERROR_EVENT = "RNBranch.initSessionError"; 48 private static final String RN_INIT_SESSION_START_EVENT = "RNBranch.initSessionStart"; 49 private static final String INIT_SESSION_SUCCESS = "INIT_SESSION_SUCCESS"; 50 private static final String INIT_SESSION_ERROR = "INIT_SESSION_ERROR"; 51 private static final String INIT_SESSION_START = "INIT_SESSION_START"; 52 53 private static final String STANDARD_EVENT_ADD_TO_CART = "STANDARD_EVENT_ADD_TO_CART"; 54 private static final String STANDARD_EVENT_ADD_TO_WISHLIST = "STANDARD_EVENT_ADD_TO_WISHLIST"; 55 private static final String STANDARD_EVENT_VIEW_CART = "STANDARD_EVENT_VIEW_CART"; 56 private static final String STANDARD_EVENT_INITIATE_PURCHASE = "STANDARD_EVENT_INITIATE_PURCHASE"; 57 private static final String STANDARD_EVENT_ADD_PAYMENT_INFO = "STANDARD_EVENT_ADD_PAYMENT_INFO"; 58 private static final String STANDARD_EVENT_PURCHASE = "STANDARD_EVENT_PURCHASE"; 59 private static final String STANDARD_EVENT_SPEND_CREDITS = "STANDARD_EVENT_SPEND_CREDITS"; 60 private static final String STANDARD_EVENT_VIEW_AD = "STANDARD_EVENT_VIEW_AD"; 61 private static final String STANDARD_EVENT_CLICK_AD = "STANDARD_EVENT_CLICK_AD"; 62 63 private static final String STANDARD_EVENT_SEARCH = "STANDARD_EVENT_SEARCH"; 64 private static final String STANDARD_EVENT_VIEW_ITEM = "STANDARD_EVENT_VIEW_ITEM"; 65 private static final String STANDARD_EVENT_VIEW_ITEMS = "STANDARD_EVENT_VIEW_ITEMS"; 66 private static final String STANDARD_EVENT_RATE = "STANDARD_EVENT_RATE"; 67 private static final String STANDARD_EVENT_SHARE = "STANDARD_EVENT_SHARE"; 68 69 private static final String STANDARD_EVENT_COMPLETE_REGISTRATION = "STANDARD_EVENT_COMPLETE_REGISTRATION"; 70 private static final String STANDARD_EVENT_COMPLETE_TUTORIAL = "STANDARD_EVENT_COMPLETE_TUTORIAL"; 71 private static final String STANDARD_EVENT_ACHIEVE_LEVEL = "STANDARD_EVENT_ACHIEVE_LEVEL"; 72 private static final String STANDARD_EVENT_UNLOCK_ACHIEVEMENT = "STANDARD_EVENT_UNLOCK_ACHIEVEMENT"; 73 private static final String STANDARD_EVENT_INVITE = "STANDARD_EVENT_INVITE"; 74 private static final String STANDARD_EVENT_LOGIN = "STANDARD_EVENT_LOGIN"; 75 private static final String STANDARD_EVENT_RESERVE = "STANDARD_EVENT_RESERVE"; 76 private static final String STANDARD_EVENT_SUBSCRIBE = "STANDARD_EVENT_SUBSCRIBE"; 77 private static final String STANDARD_EVENT_START_TRIAL = "STANDARD_EVENT_START_TRIAL"; 78 79 private static final String IDENT_FIELD_NAME = "ident"; 80 public static final String UNIVERSAL_OBJECT_NOT_FOUND_ERROR_CODE = "RNBranch::Error::BUONotFound"; 81 public static final String GENERIC_ERROR = "RNBranch::Error"; 82 private static final long AGING_HASH_TTL = 3600000; 83 private static final String PLUGIN_NAME = "ReactNative"; 84 85 private static JSONObject initSessionResult = null; 86 private BroadcastReceiver mInitSessionFinishedEventReceiver = null; 87 private BroadcastReceiver mInitSessionStartedEventReceiver = null; 88 private static Branch.BranchUniversalReferralInitListener initListener = null; 89 90 private static Activity mActivity = null; 91 private static boolean mUseDebug = false; 92 private static boolean mInitialized = false; 93 private static volatile boolean mNewIntent = true; 94 private static JSONObject mRequestMetadata = new JSONObject(); 95 96 private AgingHash<String, BranchUniversalObject> mUniversalObjectMap = new AgingHash<>(AGING_HASH_TTL); 97 98 private static Branch.BranchReferralInitListener referralInitListener = null; 99 getAutoInstance(Context context)100 public static void getAutoInstance(Context context) { 101 RNBranchConfig config = new RNBranchConfig(context); 102 String branchKey = config.getBranchKey(); 103 String liveKey = config.getLiveKey(); 104 String testKey = config.getTestKey(); 105 boolean useTest = config.getUseTestInstance(); 106 107 Branch.registerPlugin(PLUGIN_NAME, expo.modules.branch.BuildConfig.RNBRANCH_VERSION); 108 109 if (branchKey != null) { 110 Branch.getAutoInstance(context, branchKey); 111 } 112 else if (useTest && testKey != null) { 113 Branch.getAutoInstance(context, testKey); 114 } 115 else if (!useTest && liveKey != null) { 116 Branch.getAutoInstance(context, liveKey); 117 } 118 else { 119 Branch.getAutoInstance(context); 120 } 121 } 122 reInitSession(Activity reactActivity)123 public static void reInitSession(Activity reactActivity) { 124 Branch branch = Branch.getInstance(); 125 Intent intent = reactActivity.getIntent(); 126 if (intent != null) { 127 intent.putExtra("branch_force_new_session", true); 128 notifyJSOfInitSessionStart(reactActivity, intent.getData()); 129 Branch.sessionBuilder(reactActivity).withCallback(referralInitListener).reInit(); 130 } else { 131 Log.w(REACT_CLASS, "reInitSession was called but the Intent is null"); 132 } 133 } 134 initSession(final Uri uri, Activity reactActivity, Branch.BranchUniversalReferralInitListener anInitListener)135 public static void initSession(final Uri uri, Activity reactActivity, Branch.BranchUniversalReferralInitListener anInitListener) { 136 initListener = anInitListener; 137 initSession(uri, reactActivity); 138 } 139 initSession(final Uri uri, Activity reactActivity)140 public static void initSession(final Uri uri, Activity reactActivity) { 141 Branch branch = setupBranch(reactActivity.getApplicationContext()); 142 143 mActivity = reactActivity; 144 final boolean isNewIntent = mNewIntent; 145 referralInitListener = new Branch.BranchReferralInitListener(){ 146 147 private Activity mmActivity = null; 148 149 @Override 150 public void onInitFinished(JSONObject referringParams, BranchError error) { 151 // react native currently expects this to never be null 152 if (referringParams == null) { 153 referringParams = new JSONObject(); 154 } 155 156 Log.d(REACT_CLASS, "onInitFinished"); 157 JSONObject result = new JSONObject(); 158 159 try { 160 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS, referringParams); 161 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR, error != null ? error.getMessage() : JSONObject.NULL); 162 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_URI, isNewIntent && uri != null ? uri.toString() : JSONObject.NULL); 163 } 164 catch (JSONException e) { 165 166 } 167 initSessionResult = result; 168 169 BranchUniversalObject branchUniversalObject = BranchUniversalObject.getReferredBranchUniversalObject(); 170 LinkProperties linkProperties = LinkProperties.getReferredLinkProperties(); 171 172 if (initListener != null) { 173 initListener.onInitFinished(branchUniversalObject, linkProperties, error); 174 } 175 generateLocalBroadcast(referringParams, uri, branchUniversalObject, linkProperties, error); 176 } 177 178 private Branch.BranchReferralInitListener init(Activity activity) { 179 mmActivity = activity; 180 return this; 181 } 182 183 private void generateLocalBroadcast(JSONObject referringParams, 184 Uri uri, 185 BranchUniversalObject branchUniversalObject, 186 LinkProperties linkProperties, 187 BranchError error) { 188 Intent broadcastIntent = new Intent(NATIVE_INIT_SESSION_FINISHED_EVENT); 189 190 if (referringParams != null) { 191 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS, referringParams.toString()); 192 } 193 194 if (branchUniversalObject != null) { 195 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_BRANCH_UNIVERSAL_OBJECT, branchUniversalObject); 196 } 197 198 if (linkProperties != null) { 199 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_LINK_PROPERTIES, linkProperties); 200 } 201 202 /* 203 * isNewIntent is a capture of the value of mNewIntent above, so does not change when 204 * mNewIntent changes in onNewIntent. 205 */ 206 if (isNewIntent && uri != null) { 207 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_URI, uri.toString()); 208 } 209 210 if (error != null) { 211 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR, error.getMessage()); 212 } 213 214 LocalBroadcastManager.getInstance(mmActivity).sendBroadcast(broadcastIntent); 215 } 216 }.init(reactActivity); 217 218 notifyJSOfInitSessionStart(reactActivity, uri); 219 Branch.sessionBuilder(reactActivity).withCallback(referralInitListener).withData(uri).init(); 220 } 221 222 /** 223 * Call from Activity.onNewIntent: 224 * @Override 225 * public void onNewIntent(Intent intent) { 226 * super.onNewIntent(intent); 227 * RNBranchModule.onNewIntent(intent); 228 * } 229 * @param intent the new Intent received via Activity.onNewIntent 230 */ onNewIntent(@onnull Intent intent)231 public static void onNewIntent(@Nonnull Intent intent) { 232 mActivity.setIntent(intent); 233 mNewIntent = true; 234 reInitSession(mActivity); 235 } 236 237 /** 238 * Notify JavaScript of init session start. This generates an RNBranch.initSessionStart 239 * event to JS via the RN native event emitter. 240 * @param context a Context for the LocalBroadcastManager 241 * @param uri the URI to include in the notification or null 242 */ notifyJSOfInitSessionStart(Context context, Uri uri)243 private static void notifyJSOfInitSessionStart(Context context, Uri uri) { 244 /* 245 * This check just ensures that we only generate one RNBranch.initSessionStart 246 * event per call to onNewIntent(). 247 */ 248 if (!mNewIntent) return; 249 mNewIntent = false; 250 251 Intent broadcastIntent = new Intent(NATIVE_INIT_SESSION_STARTED_EVENT); 252 if (uri != null) { 253 broadcastIntent.putExtra(NATIVE_INIT_SESSION_STARTED_EVENT_URI, uri); 254 } 255 256 LocalBroadcastManager.getInstance(context).sendBroadcast(broadcastIntent); 257 Log.d(REACT_CLASS, "Sent session start broadcast for " + uri); 258 } 259 setDebug()260 public static void setDebug() { 261 mUseDebug = true; 262 } 263 setRequestMetadata(String key, String val)264 public static void setRequestMetadata(String key, String val) { 265 if (key == null) { 266 return; 267 } 268 269 if (mRequestMetadata.has(key) && val == null) { 270 mRequestMetadata.remove(key); 271 } 272 273 try { 274 mRequestMetadata.put(key, val); 275 } catch (JSONException e) { 276 // no-op 277 } 278 } 279 RNBranchModule(ReactApplicationContext reactContext)280 public RNBranchModule(ReactApplicationContext reactContext) { 281 super(reactContext); 282 listenForInitSessionEventsToReactNative(reactContext); 283 } 284 285 @javax.annotation.Nullable 286 @Override getConstants()287 public Map<String, Object> getConstants() { 288 final Map<String, Object> constants = new HashMap<>(); 289 // RN events transmitted to JS 290 291 constants.put(INIT_SESSION_SUCCESS, RN_INIT_SESSION_SUCCESS_EVENT); 292 constants.put(INIT_SESSION_ERROR, RN_INIT_SESSION_ERROR_EVENT); 293 constants.put(INIT_SESSION_START, RN_INIT_SESSION_START_EVENT); 294 295 // constants for use with BranchEvent 296 297 // Commerce events 298 299 constants.put(STANDARD_EVENT_ADD_TO_CART, BRANCH_STANDARD_EVENT.ADD_TO_CART.getName()); 300 constants.put(STANDARD_EVENT_ADD_TO_WISHLIST, BRANCH_STANDARD_EVENT.ADD_TO_WISHLIST.getName()); 301 constants.put(STANDARD_EVENT_VIEW_CART, BRANCH_STANDARD_EVENT.VIEW_CART.getName()); 302 constants.put(STANDARD_EVENT_INITIATE_PURCHASE, BRANCH_STANDARD_EVENT.INITIATE_PURCHASE.getName()); 303 constants.put(STANDARD_EVENT_ADD_PAYMENT_INFO, BRANCH_STANDARD_EVENT.ADD_PAYMENT_INFO.getName()); 304 constants.put(STANDARD_EVENT_PURCHASE, BRANCH_STANDARD_EVENT.PURCHASE.getName()); 305 constants.put(STANDARD_EVENT_SPEND_CREDITS, BRANCH_STANDARD_EVENT.SPEND_CREDITS.getName()); 306 constants.put(STANDARD_EVENT_VIEW_AD, BRANCH_STANDARD_EVENT.VIEW_AD.getName()); 307 constants.put(STANDARD_EVENT_CLICK_AD, BRANCH_STANDARD_EVENT.CLICK_AD.getName()); 308 309 // Content Events 310 311 constants.put(STANDARD_EVENT_SEARCH, BRANCH_STANDARD_EVENT.SEARCH.getName()); 312 constants.put(STANDARD_EVENT_VIEW_ITEM, BRANCH_STANDARD_EVENT.VIEW_ITEM.getName()); 313 constants.put(STANDARD_EVENT_VIEW_ITEMS , BRANCH_STANDARD_EVENT.VIEW_ITEMS.getName()); 314 constants.put(STANDARD_EVENT_RATE, BRANCH_STANDARD_EVENT.RATE.getName()); 315 constants.put(STANDARD_EVENT_SHARE, BRANCH_STANDARD_EVENT.SHARE.getName()); 316 317 // User Lifecycle Events 318 319 constants.put(STANDARD_EVENT_COMPLETE_REGISTRATION, BRANCH_STANDARD_EVENT.COMPLETE_REGISTRATION.getName()); 320 constants.put(STANDARD_EVENT_COMPLETE_TUTORIAL , BRANCH_STANDARD_EVENT.COMPLETE_TUTORIAL.getName()); 321 constants.put(STANDARD_EVENT_ACHIEVE_LEVEL, BRANCH_STANDARD_EVENT.ACHIEVE_LEVEL.getName()); 322 constants.put(STANDARD_EVENT_UNLOCK_ACHIEVEMENT, BRANCH_STANDARD_EVENT.UNLOCK_ACHIEVEMENT.getName()); 323 constants.put(STANDARD_EVENT_INVITE, BRANCH_STANDARD_EVENT.INVITE.getName()); 324 constants.put(STANDARD_EVENT_LOGIN , BRANCH_STANDARD_EVENT.LOGIN.getName()); 325 constants.put(STANDARD_EVENT_RESERVE, BRANCH_STANDARD_EVENT.RESERVE.getName()); 326 constants.put(STANDARD_EVENT_SUBSCRIBE, BRANCH_STANDARD_EVENT.SUBSCRIBE.getName()); 327 constants.put(STANDARD_EVENT_START_TRIAL, BRANCH_STANDARD_EVENT.START_TRIAL.getName()); 328 329 return constants; 330 } 331 listenForInitSessionEventsToReactNative(ReactApplicationContext reactContext)332 private void listenForInitSessionEventsToReactNative(ReactApplicationContext reactContext) { 333 mInitSessionFinishedEventReceiver = new BroadcastReceiver() { 334 RNBranchModule mBranchModule; 335 336 @Override 337 public void onReceive(Context context, Intent intent) { 338 final boolean hasError = (initSessionResult.has("error") && !initSessionResult.isNull("error")); 339 final String eventName = hasError ? RN_INIT_SESSION_ERROR_EVENT : RN_INIT_SESSION_SUCCESS_EVENT; 340 mBranchModule.sendRNEvent(eventName, convertJsonToMap(initSessionResult)); 341 } 342 343 private BroadcastReceiver init(RNBranchModule branchModule) { 344 mBranchModule = branchModule; 345 return this; 346 } 347 }.init(this); 348 349 LocalBroadcastManager.getInstance(reactContext).registerReceiver(mInitSessionFinishedEventReceiver, new IntentFilter(NATIVE_INIT_SESSION_FINISHED_EVENT)); 350 351 mInitSessionStartedEventReceiver = new BroadcastReceiver() { 352 RNBranchModule mBranchModule; 353 354 @Override 355 public void onReceive(Context context, Intent intent) { 356 Uri uri = intent.getParcelableExtra(NATIVE_INIT_SESSION_STARTED_EVENT_URI); 357 WritableMap payload = new WritableNativeMap(); 358 if (uri != null) { 359 payload.putString(NATIVE_INIT_SESSION_STARTED_EVENT_URI, uri.toString()); 360 } 361 else { 362 payload.putNull(NATIVE_INIT_SESSION_STARTED_EVENT_URI); 363 } 364 mBranchModule.sendRNEvent(RN_INIT_SESSION_START_EVENT, payload); 365 } 366 367 private BroadcastReceiver init(RNBranchModule branchModule) { 368 mBranchModule = branchModule; 369 return this; 370 } 371 }.init(this); 372 373 LocalBroadcastManager.getInstance(reactContext).registerReceiver(mInitSessionStartedEventReceiver, new IntentFilter(NATIVE_INIT_SESSION_STARTED_EVENT)); 374 } 375 376 @Override onCatalystInstanceDestroy()377 public void onCatalystInstanceDestroy() { 378 LocalBroadcastManager.getInstance(getReactApplicationContext()).unregisterReceiver(mInitSessionFinishedEventReceiver); 379 LocalBroadcastManager.getInstance(getReactApplicationContext()).unregisterReceiver(mInitSessionStartedEventReceiver); 380 } 381 382 @Override getName()383 public String getName() { 384 return REACT_MODULE_NAME; 385 } 386 387 @ReactMethod disableTracking(boolean disable)388 public void disableTracking(boolean disable) { 389 Branch branch = Branch.getInstance(); 390 branch.disableTracking(disable); 391 } 392 393 @ReactMethod isTrackingDisabled(Promise promise)394 public void isTrackingDisabled(Promise promise) { 395 Branch branch = Branch.getInstance(); 396 promise.resolve(branch.isTrackingDisabled()); 397 } 398 399 @ReactMethod createUniversalObject(ReadableMap universalObjectMap, Promise promise)400 public void createUniversalObject(ReadableMap universalObjectMap, Promise promise) { 401 String ident = UUID.randomUUID().toString(); 402 BranchUniversalObject universalObject = createBranchUniversalObject(universalObjectMap); 403 mUniversalObjectMap.put(ident, universalObject); 404 405 WritableMap response = new WritableNativeMap(); 406 response.putString(IDENT_FIELD_NAME, ident); 407 promise.resolve(response); 408 } 409 410 @ReactMethod releaseUniversalObject(String ident)411 public void releaseUniversalObject(String ident) { 412 mUniversalObjectMap.remove(ident); 413 } 414 415 @ReactMethod redeemInitSessionResult(Promise promise)416 public void redeemInitSessionResult(Promise promise) { 417 promise.resolve(convertJsonToMap(initSessionResult)); 418 } 419 420 @ReactMethod getLatestReferringParams(boolean synchronous, Promise promise)421 public void getLatestReferringParams(boolean synchronous, Promise promise) { 422 Branch branch = Branch.getInstance(); 423 if (synchronous) 424 promise.resolve(convertJsonToMap(branch.getLatestReferringParamsSync())); 425 else 426 promise.resolve(convertJsonToMap(branch.getLatestReferringParams())); 427 } 428 429 @ReactMethod getFirstReferringParams(Promise promise)430 public void getFirstReferringParams(Promise promise) { 431 Branch branch = Branch.getInstance(); 432 promise.resolve(convertJsonToMap(branch.getFirstReferringParams())); 433 } 434 435 @ReactMethod setIdentity(String identity)436 public void setIdentity(String identity) { 437 Branch branch = Branch.getInstance(); 438 branch.setIdentity(identity); 439 } 440 441 @ReactMethod setRequestMetadataKey(String key, String value)442 public void setRequestMetadataKey(String key, String value) { 443 // setRequestMetadata does not do what it appears to do. Call directly to the native code. 444 Branch branch = Branch.getInstance(); 445 branch.setRequestMetadata(key, value); 446 } 447 448 @ReactMethod logout()449 public void logout() { 450 Branch branch = Branch.getInstance(); 451 branch.logout(); 452 } 453 454 @ReactMethod logEvent(ReadableArray contentItems, String eventName, ReadableMap params, Promise promise)455 public void logEvent(ReadableArray contentItems, String eventName, ReadableMap params, Promise promise) { 456 List<BranchUniversalObject> buos = new ArrayList<>(); 457 for (int i = 0; i < contentItems.size(); ++ i) { 458 String ident = contentItems.getString(i); 459 BranchUniversalObject universalObject = findUniversalObjectOrReject(ident, promise); 460 if (universalObject == null) return; 461 buos.add(universalObject); 462 } 463 464 BranchEvent event = createBranchEvent(eventName, params); 465 event.addContentItems(buos); 466 event.logEvent(mActivity); 467 promise.resolve(null); 468 } 469 470 @ReactMethod userCompletedAction(String event, ReadableMap appState)471 public void userCompletedAction(String event, ReadableMap appState) throws JSONException { 472 Branch branch = Branch.getInstance(); 473 branch.userCompletedAction(event, convertMapToJson(appState)); 474 } 475 476 @ReactMethod userCompletedActionOnUniversalObject(String ident, String event, ReadableMap state, Promise promise)477 public void userCompletedActionOnUniversalObject(String ident, String event, ReadableMap state, Promise promise) { 478 BranchUniversalObject universalObject = findUniversalObjectOrReject(ident, promise); 479 if (universalObject == null) return; 480 481 universalObject.userCompletedAction(event, convertMapToParams(state)); 482 promise.resolve(null); 483 } 484 485 @ReactMethod sendCommerceEvent(String revenue, ReadableMap metadata, final Promise promise)486 public void sendCommerceEvent(String revenue, ReadableMap metadata, final Promise promise) throws JSONException { 487 Branch branch = Branch.getInstance(); 488 489 CommerceEvent commerceEvent = new CommerceEvent(); 490 commerceEvent.setRevenue(Double.parseDouble(revenue)); 491 492 JSONObject jsonMetadata = null; 493 if (metadata != null) { 494 jsonMetadata = convertMapToJson(metadata); 495 } 496 497 branch.sendCommerceEvent(commerceEvent, jsonMetadata, null); 498 promise.resolve(null); 499 } 500 501 @ReactMethod showShareSheet(String ident, ReadableMap shareOptionsMap, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, Promise promise)502 public void showShareSheet(String ident, ReadableMap shareOptionsMap, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, Promise promise) { 503 Context context = getReactApplicationContext(); 504 505 Handler mainHandler = new Handler(context.getMainLooper()); 506 507 Runnable myRunnable = new Runnable() { 508 Promise mPm; 509 Context mContext; 510 ReadableMap shareOptionsMap, linkPropertiesMap, controlParamsMap; 511 String ident; 512 513 private Runnable init(ReadableMap _shareOptionsMap, String _ident, ReadableMap _linkPropertiesMap, ReadableMap _controlParamsMap, Promise promise, Context context) { 514 mPm = promise; 515 mContext = context; 516 shareOptionsMap = _shareOptionsMap; 517 ident = _ident; 518 linkPropertiesMap = _linkPropertiesMap; 519 controlParamsMap = _controlParamsMap; 520 return this; 521 } 522 523 @Override 524 public void run() { 525 String messageHeader = shareOptionsMap.hasKey("messageHeader") ? shareOptionsMap.getString("messageHeader") : ""; 526 String messageBody = shareOptionsMap.hasKey("messageBody") ? shareOptionsMap.getString("messageBody") : ""; 527 ShareSheetStyle shareSheetStyle = new ShareSheetStyle(mContext, messageHeader, messageBody) 528 .setCopyUrlStyle(mContext.getResources().getDrawable(android.R.drawable.ic_menu_send), "Copy", "Added to clipboard") 529 .setMoreOptionStyle(mContext.getResources().getDrawable(android.R.drawable.ic_menu_search), "Show more") 530 .addPreferredSharingOption(SharingHelper.SHARE_WITH.EMAIL) 531 .addPreferredSharingOption(SharingHelper.SHARE_WITH.TWITTER) 532 .addPreferredSharingOption(SharingHelper.SHARE_WITH.MESSAGE) 533 .addPreferredSharingOption(SharingHelper.SHARE_WITH.FACEBOOK); 534 535 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, mPm); 536 if (branchUniversalObject == null) { 537 return; 538 } 539 540 LinkProperties linkProperties = createLinkProperties(linkPropertiesMap, controlParamsMap); 541 542 branchUniversalObject.showShareSheet( 543 getCurrentActivity(), 544 linkProperties, 545 shareSheetStyle, 546 new Branch.BranchLinkShareListener() { 547 private Promise mPromise = null; 548 549 @Override 550 public void onShareLinkDialogLaunched() { 551 } 552 553 @Override 554 public void onShareLinkDialogDismissed() { 555 if(mPromise == null) { 556 return; 557 } 558 559 WritableMap map = new WritableNativeMap(); 560 map.putString("channel", null); 561 map.putBoolean("completed", false); 562 map.putString("error", null); 563 mPromise.resolve(map); 564 mPromise = null; 565 } 566 567 @Override 568 public void onLinkShareResponse(String sharedLink, String sharedChannel, BranchError error) { 569 if(mPromise == null) { 570 return; 571 } 572 573 WritableMap map = new WritableNativeMap(); 574 map.putString("channel", sharedChannel); 575 map.putBoolean("completed", true); 576 map.putString("error", (error != null ? error.getMessage() : null)); 577 mPromise.resolve(map); 578 mPromise = null; 579 } 580 @Override 581 public void onChannelSelected(String channelName) { 582 } 583 584 private Branch.BranchLinkShareListener init(Promise promise) { 585 mPromise = promise; 586 return this; 587 } 588 }.init(mPm)); 589 } 590 }.init(shareOptionsMap, ident, linkPropertiesMap, controlParamsMap, promise, context); 591 592 mainHandler.post(myRunnable); 593 } 594 595 @ReactMethod registerView(String ident, Promise promise)596 public void registerView(String ident, Promise promise) { 597 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, promise); 598 if (branchUniversalObject == null) { 599 return; 600 } 601 602 branchUniversalObject.registerView(); 603 promise.resolve(null); 604 } 605 606 @ReactMethod generateShortUrl(String ident, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, final Promise promise)607 public void generateShortUrl(String ident, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, final Promise promise) { 608 LinkProperties linkProperties = createLinkProperties(linkPropertiesMap, controlParamsMap); 609 610 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, promise); 611 if (branchUniversalObject == null) { 612 return; 613 } 614 615 branchUniversalObject.generateShortUrl(mActivity, linkProperties, new BranchLinkCreateListener() { 616 @Override 617 public void onLinkCreate(String url, BranchError error) { 618 Log.d(REACT_CLASS, "onLinkCreate " + url); 619 if (error != null) { 620 if (error.getErrorCode() == BranchError.ERR_BRANCH_DUPLICATE_URL) { 621 promise.reject("RNBranch::Error::DuplicateResourceError", error.getMessage()); 622 } 623 else { 624 promise.reject(GENERIC_ERROR, error.getMessage()); 625 } 626 return; 627 } 628 629 WritableMap map = new WritableNativeMap(); 630 map.putString("url", url); 631 promise.resolve(map); 632 } 633 }); 634 } 635 636 @ReactMethod openURL(String url, ReadableMap options)637 public void openURL(String url, ReadableMap options) { 638 if (mActivity == null) { 639 // initSession is called before JS loads. This probably indicates failure to call initSession 640 // in an activity. 641 Log.e(REACT_CLASS, "Branch native Android SDK not initialized in openURL"); 642 return; 643 } 644 645 /* 646 * Using Intent.ACTION_VIEW here will open a browser for non-Branch links unless the 647 * domain is registered in an intent-filter in the manifest. Instead specify the host 648 * Activity. 649 */ 650 Intent intent = new Intent(mActivity, mActivity.getClass()); 651 intent.setData(Uri.parse(url)); 652 intent.putExtra("branch_force_new_session", true); 653 654 mActivity.startActivity(intent); 655 } 656 createBranchEvent(String eventName, ReadableMap params)657 public static BranchEvent createBranchEvent(String eventName, ReadableMap params) { 658 BranchEvent event; 659 try { 660 BRANCH_STANDARD_EVENT standardEvent = BRANCH_STANDARD_EVENT.valueOf(eventName); 661 // valueOf on BRANCH_STANDARD_EVENT Enum has succeeded, so this is a standard event. 662 event = new BranchEvent(standardEvent); 663 } catch (IllegalArgumentException e) { 664 // The event name is not found in standard events. 665 // So use custom event mode. 666 event = new BranchEvent(eventName); 667 } 668 669 if (params.hasKey("currency")) { 670 String currencyString = params.getString("currency"); 671 CurrencyType currency = CurrencyType.getValue(currencyString); 672 if (currency != null) { 673 event.setCurrency(currency); 674 } 675 else { 676 Log.w(REACT_CLASS, "Invalid currency " + currencyString); 677 } 678 } 679 680 if (params.hasKey("transactionID")) event.setTransactionID(params.getString("transactionID")); 681 if (params.hasKey("revenue")) event.setRevenue(Double.parseDouble(params.getString("revenue"))); 682 if (params.hasKey("shipping")) event.setShipping(Double.parseDouble(params.getString("shipping"))); 683 if (params.hasKey("tax")) event.setTax(Double.parseDouble(params.getString("tax"))); 684 if (params.hasKey("coupon")) event.setCoupon(params.getString("coupon")); 685 if (params.hasKey("affiliation")) event.setAffiliation(params.getString("affiliation")); 686 if (params.hasKey("description")) event.setDescription(params.getString("description")); 687 if (params.hasKey("searchQuery")) event.setSearchQuery(params.getString("searchQuery")); 688 if (params.hasKey("alias")) event.setCustomerEventAlias(params.getString("alias")); 689 690 if (params.hasKey("customData")) { 691 ReadableMap customData = params.getMap("customData"); 692 ReadableMapKeySetIterator it = customData.keySetIterator(); 693 while (it.hasNextKey()) { 694 String key = it.nextKey(); 695 event.addCustomDataProperty(key, customData.getString(key)); 696 } 697 } 698 699 return event; 700 } 701 createLinkProperties(ReadableMap linkPropertiesMap, @Nullable ReadableMap controlParams)702 public static LinkProperties createLinkProperties(ReadableMap linkPropertiesMap, @Nullable ReadableMap controlParams){ 703 LinkProperties linkProperties = new LinkProperties(); 704 if (linkPropertiesMap.hasKey("alias")) linkProperties.setAlias(linkPropertiesMap.getString("alias")); 705 if (linkPropertiesMap.hasKey("campaign")) linkProperties.setCampaign(linkPropertiesMap.getString("campaign")); 706 if (linkPropertiesMap.hasKey("channel")) linkProperties.setChannel(linkPropertiesMap.getString("channel")); 707 if (linkPropertiesMap.hasKey("feature")) linkProperties.setFeature(linkPropertiesMap.getString("feature")); 708 if (linkPropertiesMap.hasKey("stage")) linkProperties.setStage(linkPropertiesMap.getString("stage")); 709 710 if (linkPropertiesMap.hasKey("tags")) { 711 ReadableArray tags = linkPropertiesMap.getArray("tags"); 712 for (int i=0; i<tags.size(); ++i) { 713 String tag = tags.getString(i); 714 linkProperties.addTag(tag); 715 } 716 } 717 718 if (controlParams != null) { 719 ReadableMapKeySetIterator iterator = controlParams.keySetIterator(); 720 while (iterator.hasNextKey()) { 721 String key = iterator.nextKey(); 722 Object value = getReadableMapObjectForKey(controlParams, key); 723 linkProperties.addControlParameter(key, value.toString()); 724 } 725 } 726 727 return linkProperties; 728 } 729 setupBranch(Context context)730 private static Branch setupBranch(Context context) { 731 Branch branch = Branch.getInstance(context); 732 733 if (!mInitialized) { 734 Log.i(REACT_CLASS, "Initializing Branch SDK v. " + BuildConfig.VERSION_NAME); 735 736 RNBranchConfig config = new RNBranchConfig(context); 737 738 if (mUseDebug || config.getDebugMode()) branch.setDebug(); 739 740 if (config.getEnableFacebookLinkCheck()) branch.enableFacebookAppLinkCheck(); 741 742 if (mRequestMetadata != null) { 743 Iterator keys = mRequestMetadata.keys(); 744 while (keys.hasNext()) { 745 String key = (String) keys.next(); 746 try { 747 branch.setRequestMetadata(key, mRequestMetadata.getString(key)); 748 } catch (JSONException e) { 749 // no-op 750 } 751 } 752 } 753 754 mInitialized = true; 755 } 756 757 return branch; 758 } 759 findUniversalObjectOrReject(final String ident, final Promise promise)760 private BranchUniversalObject findUniversalObjectOrReject(final String ident, final Promise promise) { 761 BranchUniversalObject universalObject = mUniversalObjectMap.get(ident); 762 763 if (universalObject == null) { 764 final String errorMessage = "BranchUniversalObject not found for ident " + ident + "."; 765 promise.reject(UNIVERSAL_OBJECT_NOT_FOUND_ERROR_CODE, errorMessage); 766 } 767 768 return universalObject; 769 } 770 createContentMetadata(ReadableMap map)771 public ContentMetadata createContentMetadata(ReadableMap map) { 772 ContentMetadata metadata = new ContentMetadata(); 773 774 if (map.hasKey("contentSchema")) { 775 BranchContentSchema schema = BranchContentSchema.valueOf(map.getString("contentSchema")); 776 metadata.setContentSchema(schema); 777 } 778 779 if (map.hasKey("quantity")) { 780 metadata.setQuantity(map.getDouble("quantity")); 781 } 782 783 if (map.hasKey("price")) { 784 double price = Double.parseDouble(map.getString("price")); 785 CurrencyType currency = null; 786 if (map.hasKey("currency")) currency = CurrencyType.valueOf(map.getString("currency")); 787 metadata.setPrice(price, currency); 788 } 789 790 if (map.hasKey("sku")) { 791 metadata.setSku(map.getString("sku")); 792 } 793 794 if (map.hasKey("productName")) { 795 metadata.setProductName(map.getString("productName")); 796 } 797 798 if (map.hasKey("productBrand")) { 799 metadata.setProductBrand(map.getString("productBrand")); 800 } 801 802 if (map.hasKey("productCategory")) { 803 ProductCategory category = getProductCategory(map.getString("productCategory")); 804 if (category != null) metadata.setProductCategory(category); 805 } 806 807 if (map.hasKey("productVariant")) { 808 metadata.setProductVariant(map.getString("productVariant")); 809 } 810 811 if (map.hasKey("condition")) { 812 ContentMetadata.CONDITION condition = ContentMetadata.CONDITION.valueOf(map.getString("condition")); 813 metadata.setProductCondition(condition); 814 } 815 816 if (map.hasKey("ratingAverage") || map.hasKey("ratingMax") || map.hasKey("ratingCount")) { 817 Double average = null, max = null; 818 Integer count = null; 819 if (map.hasKey("ratingAverage")) average = map.getDouble("ratingAverage"); 820 if (map.hasKey("ratingCount")) count = map.getInt("ratingCount"); 821 if (map.hasKey("ratingMax")) max = map.getDouble("ratingMax"); 822 metadata.setRating(average, max, count); 823 } 824 825 if (map.hasKey("addressStreet") || 826 map.hasKey("addressCity") || 827 map.hasKey("addressRegion") || 828 map.hasKey("addressCountry") || 829 map.hasKey("addressPostalCode")) { 830 String street = null, city = null, region = null, country = null, postalCode = null; 831 if (map.hasKey("addressStreet")) street = map.getString("addressStreet"); 832 if (map.hasKey("addressCity")) street = map.getString("addressCity"); 833 if (map.hasKey("addressRegion")) street = map.getString("addressRegion"); 834 if (map.hasKey("addressCountry")) street = map.getString("addressCountry"); 835 if (map.hasKey("addressPostalCode")) street = map.getString("addressPostalCode"); 836 metadata.setAddress(street, city, region, country, postalCode); 837 } 838 839 if (map.hasKey("latitude") || map.hasKey("longitude")) { 840 Double latitude = null, longitude = null; 841 if (map.hasKey("latitude")) latitude = map.getDouble("latitude"); 842 if (map.hasKey("longitude")) longitude = map.getDouble("longitude"); 843 metadata.setLocation(latitude, longitude); 844 } 845 846 if (map.hasKey("imageCaptions")) { 847 ReadableArray captions = map.getArray("imageCaptions"); 848 for (int j=0; j < captions.size(); ++j) { 849 metadata.addImageCaptions(captions.getString(j)); 850 } 851 } 852 853 if (map.hasKey("customMetadata")) { 854 ReadableMap customMetadata = map.getMap("customMetadata"); 855 ReadableMapKeySetIterator it = customMetadata.keySetIterator(); 856 while (it.hasNextKey()) { 857 String key = it.nextKey(); 858 metadata.addCustomMetadata(key, customMetadata.getString(key)); 859 } 860 } 861 862 return metadata; 863 } 864 createBranchUniversalObject(ReadableMap branchUniversalObjectMap)865 public BranchUniversalObject createBranchUniversalObject(ReadableMap branchUniversalObjectMap) { 866 BranchUniversalObject branchUniversalObject = new BranchUniversalObject() 867 .setCanonicalIdentifier(branchUniversalObjectMap.getString("canonicalIdentifier")); 868 869 if (branchUniversalObjectMap.hasKey("title")) branchUniversalObject.setTitle(branchUniversalObjectMap.getString("title")); 870 if (branchUniversalObjectMap.hasKey("canonicalUrl")) branchUniversalObject.setCanonicalUrl(branchUniversalObjectMap.getString("canonicalUrl")); 871 if (branchUniversalObjectMap.hasKey("contentDescription")) branchUniversalObject.setContentDescription(branchUniversalObjectMap.getString("contentDescription")); 872 if (branchUniversalObjectMap.hasKey("contentImageUrl")) branchUniversalObject.setContentImageUrl(branchUniversalObjectMap.getString("contentImageUrl")); 873 874 if (branchUniversalObjectMap.hasKey("locallyIndex")) { 875 if (branchUniversalObjectMap.getBoolean("locallyIndex")) { 876 branchUniversalObject.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 877 } 878 else { 879 branchUniversalObject.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 880 } 881 } 882 883 if (branchUniversalObjectMap.hasKey("publiclyIndex")) { 884 if (branchUniversalObjectMap.getBoolean("publiclyIndex")) { 885 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 886 } 887 else { 888 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 889 } 890 } 891 892 if (branchUniversalObjectMap.hasKey("contentIndexingMode")) { 893 switch (branchUniversalObjectMap.getType("contentIndexingMode")) { 894 case String: 895 String mode = branchUniversalObjectMap.getString("contentIndexingMode"); 896 897 if (mode.equals("private")) 898 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 899 else if (mode.equals("public")) 900 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 901 else 902 Log.w(REACT_CLASS, "Unsupported value for contentIndexingMode: " + mode + 903 ". Supported values are \"public\" and \"private\""); 904 break; 905 default: 906 Log.w(REACT_CLASS, "contentIndexingMode must be a String"); 907 break; 908 } 909 } 910 911 if (branchUniversalObjectMap.hasKey("currency") && branchUniversalObjectMap.hasKey("price")) { 912 String currencyString = branchUniversalObjectMap.getString("currency"); 913 CurrencyType currency = CurrencyType.valueOf(currencyString); 914 branchUniversalObject.setPrice(branchUniversalObjectMap.getDouble("price"), currency); 915 } 916 917 if (branchUniversalObjectMap.hasKey("expirationDate")) { 918 String expirationString = branchUniversalObjectMap.getString("expirationDate"); 919 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 920 format.setTimeZone(TimeZone.getTimeZone("UTC")); 921 try { 922 Date date = format.parse(expirationString); 923 Log.d(REACT_CLASS, "Expiration date is " + date.toString()); 924 branchUniversalObject.setContentExpiration(date); 925 } 926 catch (ParseException e) { 927 Log.w(REACT_CLASS, "Invalid expiration date format. Valid format is YYYY-mm-ddTHH:MM:SS, e.g. 2017-02-01T00:00:00. All times UTC."); 928 } 929 } 930 931 if (branchUniversalObjectMap.hasKey("keywords")) { 932 ReadableArray keywords = branchUniversalObjectMap.getArray("keywords"); 933 for (int i=0; i<keywords.size(); ++i) { 934 branchUniversalObject.addKeyWord(keywords.getString(i)); 935 } 936 } 937 938 if(branchUniversalObjectMap.hasKey("metadata")) { 939 ReadableMap metadataMap = branchUniversalObjectMap.getMap("metadata"); 940 ReadableMapKeySetIterator iterator = metadataMap.keySetIterator(); 941 while (iterator.hasNextKey()) { 942 String metadataKey = iterator.nextKey(); 943 Object metadataObject = getReadableMapObjectForKey(metadataMap, metadataKey); 944 branchUniversalObject.addContentMetadata(metadataKey, metadataObject.toString()); 945 HashMap<String, String> metadata = branchUniversalObject.getMetadata(); 946 } 947 } 948 949 if (branchUniversalObjectMap.hasKey("type")) branchUniversalObject.setContentType(branchUniversalObjectMap.getString("type")); 950 951 if (branchUniversalObjectMap.hasKey("contentMetadata")) { 952 branchUniversalObject.setContentMetadata(createContentMetadata(branchUniversalObjectMap.getMap("contentMetadata"))); 953 } 954 955 return branchUniversalObject; 956 } 957 958 @Nullable getProductCategory(final String stringValue)959 public ProductCategory getProductCategory(final String stringValue) { 960 ProductCategory[] possibleValues = ProductCategory.class.getEnumConstants(); 961 for (ProductCategory value: possibleValues) { 962 if (stringValue.equals(value.getName())) { 963 return value; 964 } 965 } 966 Log.w(REACT_CLASS, "Could not find product category " + stringValue); 967 return null; 968 } 969 970 @ReactMethod redeemRewards(int value, String bucket, Promise promise)971 public void redeemRewards(int value, String bucket, Promise promise) 972 { 973 if (bucket == null) { 974 Branch.getInstance().redeemRewards(value, new RedeemRewardsListener(promise)); 975 } else { 976 Branch.getInstance().redeemRewards(bucket, value, new RedeemRewardsListener(promise)); 977 } 978 } 979 980 @ReactMethod loadRewards(String bucket, Promise promise)981 public void loadRewards(String bucket, Promise promise) 982 { 983 Branch.getInstance().loadRewards(new LoadRewardsListener(bucket, promise)); 984 } 985 986 @ReactMethod getCreditHistory(Promise promise)987 public void getCreditHistory(Promise promise) 988 { 989 Branch.getInstance().getCreditHistory(new CreditHistoryListener(promise)); 990 } 991 992 protected class CreditHistoryListener implements Branch.BranchListResponseListener 993 { 994 private Promise _promise; 995 996 // Constructor that takes in a required callbackContext object CreditHistoryListener(Promise promise)997 public CreditHistoryListener(Promise promise) { 998 this._promise = promise; 999 } 1000 1001 // Listener that implements BranchListResponseListener for getCreditHistory() 1002 @Override onReceivingResponse(JSONArray list, BranchError error)1003 public void onReceivingResponse(JSONArray list, BranchError error) { 1004 ArrayList<String> errors = new ArrayList<String>(); 1005 if (error == null) { 1006 try { 1007 ReadableArray result = convertJsonToArray(list); 1008 this._promise.resolve(result); 1009 } catch (JSONException err) { 1010 this._promise.reject(GENERIC_ERROR, err.getMessage()); 1011 } 1012 } else { 1013 String errorMessage = error.getMessage(); 1014 Log.d(REACT_CLASS, errorMessage); 1015 this._promise.reject(GENERIC_ERROR, errorMessage); 1016 } 1017 } 1018 } 1019 1020 protected class RedeemRewardsListener implements Branch.BranchReferralStateChangedListener 1021 { 1022 private Promise _promise; 1023 RedeemRewardsListener(Promise promise)1024 public RedeemRewardsListener(Promise promise) { 1025 this._promise = promise; 1026 } 1027 1028 @Override onStateChanged(boolean changed, BranchError error)1029 public void onStateChanged(boolean changed, BranchError error) { 1030 if (error == null) { 1031 WritableMap map = new WritableNativeMap(); 1032 map.putBoolean("changed", changed); 1033 this._promise.resolve(map); 1034 } else { 1035 String errorMessage = error.getMessage(); 1036 Log.d(REACT_CLASS, errorMessage); 1037 this._promise.reject(GENERIC_ERROR, errorMessage); 1038 } 1039 } 1040 } 1041 1042 protected class LoadRewardsListener implements Branch.BranchReferralStateChangedListener 1043 { 1044 private String _bucket; 1045 private Promise _promise; 1046 LoadRewardsListener(String bucket, Promise promise)1047 public LoadRewardsListener(String bucket, Promise promise) { 1048 this._bucket = bucket; 1049 this._promise = promise; 1050 } 1051 1052 @Override onStateChanged(boolean changed, BranchError error)1053 public void onStateChanged(boolean changed, BranchError error) { 1054 if (error == null) { 1055 int credits = 0; 1056 if (this._bucket == null) { 1057 credits = Branch.getInstance().getCredits(); 1058 } else { 1059 credits = Branch.getInstance().getCreditsForBucket(this._bucket); 1060 } 1061 WritableMap map = new WritableNativeMap(); 1062 map.putInt("credits", credits); 1063 this._promise.resolve(map); 1064 } else { 1065 String errorMessage = error.getMessage(); 1066 Log.d(REACT_CLASS, errorMessage); 1067 this._promise.reject(GENERIC_ERROR, errorMessage); 1068 } 1069 } 1070 } 1071 sendRNEvent(String eventName, @Nullable WritableMap params)1072 public void sendRNEvent(String eventName, @Nullable WritableMap params) { 1073 // This should avoid the crash in getJSModule() at startup 1074 // See also: https://github.com/walmartreact/react-native-orientation-listener/issues/8 1075 1076 ReactApplicationContext context = getReactApplicationContext(); 1077 Handler mainHandler = new Handler(context.getMainLooper()); 1078 1079 Runnable poller = new Runnable() { 1080 1081 private Runnable init(ReactApplicationContext _context, Handler _mainHandler, String _eventName, WritableMap _params) { 1082 mMainHandler = _mainHandler; 1083 mEventName = _eventName; 1084 mContext = _context; 1085 mParams = _params; 1086 return this; 1087 } 1088 1089 final int pollDelayInMs = 100; 1090 final int maxTries = 300; 1091 1092 int tries = 1; 1093 String mEventName; 1094 WritableMap mParams; 1095 Handler mMainHandler; 1096 ReactApplicationContext mContext; 1097 1098 @Override 1099 public void run() { 1100 try { 1101 Log.d(REACT_CLASS, "Catalyst instance poller try " + Integer.toString(tries)); 1102 if (mContext.hasActiveCatalystInstance()) { 1103 Log.d(REACT_CLASS, "Catalyst instance active"); 1104 mContext 1105 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 1106 .emit(mEventName, mParams); 1107 } else { 1108 tries++; 1109 if (tries <= maxTries) { 1110 mMainHandler.postDelayed(this, pollDelayInMs); 1111 } else { 1112 Log.e(REACT_CLASS, "Could not get Catalyst instance"); 1113 } 1114 } 1115 } 1116 catch (Exception e) { 1117 e.printStackTrace(); 1118 } 1119 } 1120 }.init(context, mainHandler, eventName, params); 1121 1122 Log.d(REACT_CLASS, "sendRNEvent"); 1123 1124 mainHandler.post(poller); 1125 } 1126 getReadableMapObjectForKey(ReadableMap readableMap, String key)1127 private static Object getReadableMapObjectForKey(ReadableMap readableMap, String key) { 1128 switch (readableMap.getType(key)) { 1129 case Null: 1130 return "Null"; 1131 case Boolean: 1132 return readableMap.getBoolean(key); 1133 case Number: 1134 if (readableMap.getDouble(key) % 1 == 0) { 1135 return readableMap.getInt(key); 1136 } else { 1137 return readableMap.getDouble(key); 1138 } 1139 case String: 1140 return readableMap.getString(key); 1141 default: 1142 return "Unsupported Type"; 1143 } 1144 } 1145 convertMapToJson(ReadableMap readableMap)1146 private static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException { 1147 JSONObject object = new JSONObject(); 1148 ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 1149 while (iterator.hasNextKey()) { 1150 String key = iterator.nextKey(); 1151 switch (readableMap.getType(key)) { 1152 case Null: 1153 object.put(key, JSONObject.NULL); 1154 break; 1155 case Boolean: 1156 object.put(key, readableMap.getBoolean(key)); 1157 break; 1158 case Number: 1159 object.put(key, readableMap.getDouble(key)); 1160 break; 1161 case String: 1162 object.put(key, readableMap.getString(key)); 1163 break; 1164 case Map: 1165 object.put(key, convertMapToJson(readableMap.getMap(key))); 1166 break; 1167 case Array: 1168 object.put(key, convertArrayToJson(readableMap.getArray(key))); 1169 break; 1170 } 1171 } 1172 return object; 1173 } 1174 convertArrayToJson(ReadableArray readableArray)1175 private static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException { 1176 JSONArray array = new JSONArray(); 1177 for (int i = 0; i < readableArray.size(); i++) { 1178 switch (readableArray.getType(i)) { 1179 case Null: 1180 break; 1181 case Boolean: 1182 array.put(readableArray.getBoolean(i)); 1183 break; 1184 case Number: 1185 array.put(readableArray.getDouble(i)); 1186 break; 1187 case String: 1188 array.put(readableArray.getString(i)); 1189 break; 1190 case Map: 1191 array.put(convertMapToJson(readableArray.getMap(i))); 1192 break; 1193 case Array: 1194 array.put(convertArrayToJson(readableArray.getArray(i))); 1195 break; 1196 } 1197 } 1198 return array; 1199 } 1200 convertJsonToMap(JSONObject jsonObject)1201 private static WritableMap convertJsonToMap(JSONObject jsonObject) { 1202 if(jsonObject == null) { 1203 return null; 1204 } 1205 1206 WritableMap map = new WritableNativeMap(); 1207 1208 try { 1209 Iterator<String> iterator = jsonObject.keys(); 1210 while (iterator.hasNext()) { 1211 String key = iterator.next(); 1212 Object value = jsonObject.get(key); 1213 if (value instanceof JSONObject) { 1214 map.putMap(key, convertJsonToMap((JSONObject) value)); 1215 } else if (value instanceof JSONArray) { 1216 map.putArray(key, convertJsonToArray((JSONArray) value)); 1217 } else if (value instanceof Boolean) { 1218 map.putBoolean(key, (Boolean) value); 1219 } else if (value instanceof Integer) { 1220 map.putInt(key, (Integer) value); 1221 } else if (value instanceof Double) { 1222 map.putDouble(key, (Double) value); 1223 } else if (value instanceof String) { 1224 map.putString(key, (String) value); 1225 } else if (value == null || value == JSONObject.NULL) { 1226 map.putNull(key); 1227 } else { 1228 map.putString(key, value.toString()); 1229 } 1230 } 1231 } catch(JSONException ex) { 1232 map.putString("error", "Failed to convert JSONObject to WriteableMap: " + ex.getMessage()); 1233 } 1234 1235 return map; 1236 } 1237 convertJsonToArray(JSONArray jsonArray)1238 private static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONException { 1239 WritableArray array = new WritableNativeArray(); 1240 1241 for (int i = 0; i < jsonArray.length(); i++) { 1242 Object value = jsonArray.get(i); 1243 if (value instanceof JSONObject) { 1244 array.pushMap(convertJsonToMap((JSONObject) value)); 1245 } else if (value instanceof JSONArray) { 1246 array.pushArray(convertJsonToArray((JSONArray) value)); 1247 } else if (value instanceof Boolean) { 1248 array.pushBoolean((Boolean) value); 1249 } else if (value instanceof Integer) { 1250 array.pushInt((Integer) value); 1251 } else if (value instanceof Double) { 1252 array.pushDouble((Double) value); 1253 } else if (value instanceof String) { 1254 array.pushString((String) value); 1255 } else { 1256 array.pushString(value.toString()); 1257 } 1258 } 1259 return array; 1260 } 1261 1262 // Convert an arbitrary ReadableMap to a string-string hash of custom params for userCompletedAction. convertMapToParams(ReadableMap map)1263 private static HashMap<String, String> convertMapToParams(ReadableMap map) { 1264 if (map == null) return null; 1265 1266 HashMap<String, String> hash = new HashMap<>(); 1267 1268 ReadableMapKeySetIterator iterator = map.keySetIterator(); 1269 while (iterator.hasNextKey()) { 1270 String key = iterator.nextKey(); 1271 switch (map.getType(key)) { 1272 case String: 1273 hash.put(key, map.getString(key)); 1274 case Boolean: 1275 hash.put(key, "" + map.getBoolean(key)); 1276 case Number: 1277 hash.put(key, "" + map.getDouble(key)); 1278 default: 1279 Log.w(REACT_CLASS, "Unsupported data type in params, ignoring"); 1280 } 1281 } 1282 1283 return hash; 1284 } 1285 } 1286