diff --git a/BridgeAPI.md b/BridgeAPI.md index 0d955de..b8f7937 100644 --- a/BridgeAPI.md +++ b/BridgeAPI.md @@ -28,6 +28,9 @@ native modules for both platforms: Android and iOS. - getLookupData - getExperiences - experienceShown +- getPlacement +- placementImpression +- placementClickthrough ------------------------------------------------------- ## **init(trackingId, logLevel)** @@ -293,3 +296,109 @@ None ### Example QubitSDK.experienceShown("https://sse.qubit.com/v1/callback?data=igK....n0="); + +------------------------------------------------------- + +## **getPlacement**(placementId, mode, attributes, campaignId, experienceId, placementPromise) + +### Description +Returns Placement for given parameters. + +### Parameters +- placementId + - Type: String + - Constraints: Not null + - Description: Unique ID of the placement. +- mode + - Type: String + - Constraints: Can be one of LIVE/SAMPLE/PREVIEW. + - Description: The mode to fetch placements content with. Defaults to LIVE. +- attributes + - Type: String + - Constraints: Should be string description of JSON or null + - Description: JSON string containing custom attributes to be used to query for the placement. "visitor" attribute will be ignored as it is set by SDK. +- campaignId + - Type: String + - Constraints: Nullable + - Description: Campaign identifier +- experienceId + - Type: String + - Constraints: Nullable + - Description: Experience identifier + +### Result +Promise with a map describing Placement object: + + { + "content": { ... } + "impressionUrl": "https://api.qubit.com/placements/callback?data=ggW4eyJtZXRhIjp7ImlkIjo", + "clickthroughUrl": "https://api.qubit.com/placements/callback?data=mQW4eyJtZXRhIjp7Imlkx" + } +The structure of response content depends on the type of placement that is being called. + +### Exceptions +- Exception is thrown, when SDK is not initialized. + +### Example + async () => { + const placement = await QubitSDK.getPlacement( + "placement_id", + "LIVE", + "{ \"color\": \"blue\"}", + "campaign_id", + "experience_id" + ); + ... + } + +------------------------------------------------------- + +## placementImpression(callbackUrl) + +### Description +Sends request to URL described by placement impression callback. + +### Parameters +- callbackUrl + - Type: String + - Constraints: Not null + - Description: Impression callback URL. + + +### Result +None + +### Example + async () => { + const placement = await QubitSDK.placementImpression( + "https://some.url.com" + ); + ... + } + +------------------------------------------------------- + +## placementClickthrough(callbackUrl) + +### Description +Sends request to URL described by placement clickthrough callback. + +### Parameters +- callbackUrl + - Type: String + - Constraints: Not null + - Description: Clickthrough callback URL. + + +### Result +None + +### Example + async () => { + const placement = await QubitSDK.placementClickthrough( + "https://some.url.com" + ); + ... + } + + diff --git a/README.md b/README.md index fe7f2ae..1d72e34 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Installation of the QubitSDK, to provide event tracking and lookup. To make use ### Installation -1. `$ npm install qubit-sdk-react-native --save` -or -`$ yarn add qubit-sdk-react-native` +1. `$ npm install qubit-sdk-react-native --save` + or + `$ yarn add qubit-sdk-react-native` -2. Navigate to your `/ios` directory and run `pod install` to ensure the `QubitSDK` CocoaPod is installed. Android should require no further installation. +2. Navigate to your `/ios` directory and run `pod install` to ensure the `QubitSDK` CocoaPod is installed. Android should require no further installation. Optional - if you are using React Native < 0.60, you must `link` the library. @@ -54,6 +54,9 @@ and send first event - [getExperiences](#getexperiences) - [Parameters](#parameters-3) - [Examples](#examples-6) +- [getPlacement](#getplacement) + - [Parameters](#parameters-4) + - [Examples](#examples-7) #### start @@ -214,6 +217,42 @@ async () => { Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<Experience>>** Promise with an array of Experience objects. +#### getPlacement + +Returns Placement for given parameters. + +##### Parameters + +- `placementId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Unique ID of the placement. +- `mode` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The mode to fetch placements content with, can be one of LIVE/SAMPLE/PREVIEW. Defaults to LIVE. +- `attributes` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** JSON string containing custom attributes to be used to query for the placement. "visitor" attribute will be ignored as it is set by SDK. +- `campaignId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Optional. +- `experienceId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Optional. + +##### Examples + +```javascript +async () => { + const placement = await getPlacement( + "placement_id", + "LIVE", + "{ \"color\": \"blue\"}", + "campaign_id", + "experience_id" + ); + ... + placement.impression(); + ... + placement.clickthrough(); +} + +{ + "content": { ... } +} +``` + +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<Placement>** Promise with an object describing Placement object. + ### Compatibility Qubit SDK React Native is compatible with React Native 0.58 and higher diff --git a/android/build.gradle b/android/build.gradle index c60967d..9bab2ba 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -58,6 +58,6 @@ dependencies { implementation "androidx.annotation:annotation:1.0.0" implementation "com.google.code.gson:gson:2.8.2" - implementation 'com.qubit:qubit-sdk-android:1.4.1' + implementation 'com.qubit:qubit-sdk-android:2.0.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.0" } diff --git a/android/src/main/java/com/qubit/reactnative/sdk/QubitSDKModule.java b/android/src/main/java/com/qubit/reactnative/sdk/QubitSDKModule.java index 7cca174..a94c632 100644 --- a/android/src/main/java/com/qubit/reactnative/sdk/QubitSDKModule.java +++ b/android/src/main/java/com/qubit/reactnative/sdk/QubitSDKModule.java @@ -1,7 +1,7 @@ package com.qubit.reactnative.sdk; import android.util.Log; -import androidx.annotation.NonNull; + import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -12,18 +12,24 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.qubit.android.sdk.api.QubitSDK; import com.qubit.android.sdk.api.logging.QBLogLevel; +import com.qubit.android.sdk.api.placement.PlacementMode; +import com.qubit.android.sdk.api.placement.PlacementPreviewOptions; import com.qubit.android.sdk.api.tracker.event.QBEvent; import com.qubit.android.sdk.api.tracker.event.QBEvents; import com.qubit.android.sdk.internal.experience.Experience; -import com.qubit.android.sdk.internal.experience.callback.CallbackConnector; -import com.qubit.android.sdk.internal.experience.callback.CallbackConnectorImpl; +import com.qubit.android.sdk.internal.experience.callback.ExperienceCallbackConnector; +import com.qubit.android.sdk.internal.experience.callback.ExperienceCallbackConnectorImpl; import com.qubit.android.sdk.internal.experience.model.ExperiencePayload; import com.qubit.android.sdk.internal.lookup.LookupData; + import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; + public class QubitSDKModule extends ReactContextBaseJavaModule { private static ReactApplicationContext reactContext; @@ -139,10 +145,68 @@ public void getExperiences(ReadableArray experienceIds, @ReactMethod public void experienceShown(String callback) { - CallbackConnector callbackConnector = new CallbackConnectorImpl(callback, QubitSDK.getDeviceId()); + ExperienceCallbackConnector callbackConnector = new ExperienceCallbackConnectorImpl(callback, QubitSDK.getDeviceId()); callbackConnector.shown(); } + @ReactMethod + public void getPlacement( + String placementId, + String mode, + String attributes, + String campaignId, + String experienceId, + Promise placementPromise + ) { + QubitSDK.getPlacement( + placementId, + matchMode(mode), + getAttributesJson(attributes), + new PlacementPreviewOptions(campaignId, experienceId), + placement -> { + JsonObject placementJson = new JsonObject(); + placementJson.add("content", placement.getContent()); + placementJson.addProperty("impressionUrl", placement.getImpressionUrl()); + placementJson.addProperty("clickthroughUrl", placement.getClickthroughUrl()); + placementPromise.resolve(WritableMapConverter.convertJsonToMap(placementJson)); + return null; + }, + throwable -> { + placementPromise.reject(throwable); + return null; + } + ); + } + + @ReactMethod + public void placementImpression(String callbackUrl) { + QubitSDK.sendCallbackRequest(callbackUrl); + } + + @ReactMethod + public void placementClickthrough(String callbackUrl) { + QubitSDK.sendCallbackRequest(callbackUrl); + } + + private PlacementMode matchMode(String value) { + switch (value) { + case "SAMPLE": + return PlacementMode.SAMPLE; + case "PREVIEW": + return PlacementMode.PREVIEW; + case "LIVE": + default: + return PlacementMode.LIVE; + } + } + + private JsonObject getAttributesJson(String attributes) { + try { + return new JsonParser().parse(attributes).getAsJsonObject(); + } catch (Exception e) { + return null; + } + } private static QBLogLevel defaultLogLevel = QBLogLevel.WARN; @@ -150,7 +214,7 @@ private QBLogLevel parseLogLevel(String logLevel) { if (logLevel == null || logLevel.isEmpty()) { return defaultLogLevel; } - for(QBLogLevel level : QBLogLevel.values()) { + for (QBLogLevel level : QBLogLevel.values()) { if (level.toString().equalsIgnoreCase(logLevel)) return level; } diff --git a/example/App.js b/example/App.js index eb1a741..f02dabd 100644 --- a/example/App.js +++ b/example/App.js @@ -42,6 +42,39 @@ class App extends PureComponent { exp.forEach(e => console.log(e)); }; + getPlacement = async () => { + const placement = await QubitSDK.getPlacement( + "tsOujouCSSKJGSCMUsmQRw", + "LIVE", + null, + "1ybrhki9RvKWpA-9veLQSg", + null + ); + console.log(placement); + }; + + sendPlacementImpression = async () => { + const placement = await QubitSDK.getPlacement( + "tsOujouCSSKJGSCMUsmQRw", + "LIVE", + null, + "1ybrhki9RvKWpA-9veLQSg", + null + ); + placement.impression(); + }; + + sendPlacementClickthrough = async () => { + const placement = await QubitSDK.getPlacement( + "tsOujouCSSKJGSCMUsmQRw", + "LIVE", + null, + "1ybrhki9RvKWpA-9veLQSg", + null + ); + placement.clickthrough(); + }; + render() { return ( <> @@ -82,6 +115,21 @@ class App extends PureComponent { Get experiences + + + Get placement + + + + + Send placement impression callback + + + + + Send placement clickthrough callback + + ) diff --git a/ios/QubitSDKModule/QubitSDKModule.m b/ios/QubitSDKModule/QubitSDKModule.m index 392c7ed..e2995a0 100644 --- a/ios/QubitSDKModule/QubitSDKModule.m +++ b/ios/QubitSDKModule/QubitSDKModule.m @@ -28,5 +28,15 @@ @interface RCT_EXTERN_REMAP_MODULE(QubitSDK, QubitSDKModule, NSObject) resolver:(RCTPromiseResolveBlock) resolver rejecter:(RCTPromiseRejectBlock) rejecter) RCT_EXTERN_METHOD(experienceShown:(NSString *) callback) +RCT_EXTERN_METHOD(getPlacement: + (NSString *) placementId + mode:(NSString *) mode + attributes:(NSString *) attributes + campaignId:(NSString *) campaignId + experienceId:(NSString *) experienceId + resolver:(RCTPromiseResolveBlock) resolver + rejecter:(RCTPromiseRejectBlock) rejecter) +RCT_EXTERN_METHOD(placementImpression:(NSString *) callback) +RCT_EXTERN_METHOD(placementClickthrough:(NSString *) callback) @end diff --git a/ios/QubitSDKModule/QubitSDKModule.swift b/ios/QubitSDKModule/QubitSDKModule.swift index 5fc9747..e6a597f 100644 --- a/ios/QubitSDKModule/QubitSDKModule.swift +++ b/ios/QubitSDKModule/QubitSDKModule.swift @@ -61,6 +61,30 @@ class QubitSDKModule: NSObject { func experienceShown(callback: String) { QBExperienceEntityCallback(callback: callback).shown() } + + @objc(getPlacement:mode:attributes:campaignId:experienceId:resolver:rejecter:) + func getPlacement(placementId: String, mode: String, attributes: String, campaignId: String, experienceId: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { + QubitSDK.getPlacement(with: placementId, + mode: mode, + attributes: attributes, + campaignId: campaignId, + experienceId: experienceId, + onSuccess: { result in + resolver( result.asDictionary ) + }, onError: { error in + rejecter("Error", "QubitSDKModule: getPlacement failed.", error) + }) + } + + @objc(placementImpression:) + func placementImpression(callback: String) { + QBPlacementEntityCallback(impressionUrl: callback).impression() + } + + @objc(placementClickthrough:) + func placementClickthrough(callback: String) { + QBPlacementEntityCallback(clickthroughUrl: callback).clickthrough() + } @objc static func requiresMainQueueSetup() -> Bool { diff --git a/package.json b/package.json index 3024298..415c55f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qubit-sdk-react-native", - "version": "1.0.7", + "version": "2.0.0", "description": "React Native bridge for using native Qubit SDK libraries on iOS and Android", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/qubit-sdk-react-native.podspec b/qubit-sdk-react-native.podspec index c2a0c66..b24cbe8 100644 --- a/qubit-sdk-react-native.podspec +++ b/qubit-sdk-react-native.podspec @@ -20,6 +20,6 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" - s.dependency "QubitSDK" - s.swift_versions = ['4.0', '4.1', '4.2', '5.0'] + s.dependency "QubitSDK", "~> 2.0.0" + s.swift_versions = ['4.0', '4.1', '4.2', '5.0', '5.1', '5.2', '5.3'] end diff --git a/src/index.ts b/src/index.ts index 671af27..45f1ff7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,10 +16,16 @@ type Experience = { isControl: boolean, id: number, callback: string, - variation: number + variation: number, shown: () => void } +type Placement = { + content: object, + impression: () => void, + clickthrough: () => void +} + class QubitSDK { /** * Initialization of SDK. It should be called as early as possible after application start, only once and before any other interaction with the API. @@ -183,7 +189,59 @@ class QubitSDK { !(ignoreSegments == null), ignoreSegments || false ) - .then(experiences => experiences.map(e => ({...e, shown: () => { NativeModules.QubitSDK.experienceShown(e.callback || '')} }))) + .then(experiences => experiences.map(e => ({ + ...e, + shown: () => { NativeModules.QubitSDK.experienceShown(e.callback || '') } + }))) + } + + /** + * Returns Placement for given parameters. + * @param {string} placementId Unique ID of the placement. + * @param {string} [mode] The mode to fetch placements content with, can be one of LIVE/SAMPLE/PREVIEW. Defaults to LIVE. + * @param {string} [attributes] JSON string containing custom attributes to be used to query for the placement. "visitor" attribute will be ignored as it is set by SDK. + * @param {string} [campaignId] Optional. + * @param {string} [experienceId] Optional. + * @returns {Promise} Promise with an object describing Placement object. + * @example + * + * async () => { + * const placement = await getPlacement( + * "placement_id", + * "LIVE", + * "{ \"color\": \"blue\"}", + * "campaign_id", + * "experience_id" + * ); + * ... + * placement.impression(); + * ... + * placement.clickthrough(); + * } + * + * { + * "content": { ... } + * } + */ + public getPlacement( + placementId: string, + mode?: string, + attributes?: string, + campaignId?: string, + experienceId?: string + ) : Promise { + return NativeModules.QubitSDK.getPlacement( + placementId, + mode, + attributes, + campaignId, + experienceId + ) + .then(placement => ({ + content: placement.content, + impression: () => { NativeModules.QubitSDK.placementImpression(placement.impressionUrl || '') }, + clickthrough: () => { NativeModules.QubitSDK.placementClickthrough(placement.clickthroughUrl || '') } + })) } }