Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions Singular-React-Native.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,34 @@ Pod::Spec.new do |spec|
spec.homepage = "https://www.singular.net/"
spec.author = "Singular Labs"
spec.source = { :git => "https://github.com/singular-labs/react-native-sdk.git", :tag => spec.version.to_s }
spec.source_files = "ios/*.{h,m}"
# Source files based on architecture
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
spec.source_files = "ios/SingularBridge.h", "ios/SingularBridgeNewArch.mm", "ios/SingularHelper.h", "ios/SingularHelper.m"
else
spec.source_files = "ios/SingularBridge.h", "ios/SingularBridgeOldArch.m", "ios/SingularHelper.h", "ios/SingularHelper.m"
end
spec.platform = :ios, "12.0"
spec.dependency 'Singular-SDK', '12.8.1'
spec.dependency 'Singular-SDK', '12.9.0'
spec.static_framework = true

spec.dependency 'React'

# New Architecture support
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
spec.compiler_flags = "-DRCT_NEW_ARCH_ENABLED=1"
spec.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost" "${PODS_TARGET_SRCROOT}/../../ios/build/generated/ios"',
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
"CLANG_CXX_LIBRARY" => "libc++",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1"
}
spec.dependency "React-Codegen"
spec.dependency "RCT-Folly"
spec.dependency "RCTRequired"
spec.dependency "RCTTypeSafety"
spec.dependency "ReactCommon/turbomodule/core"
else
spec.compiler_flags = "-DRCT_NEW_ARCH_ENABLED=0"
end
end

297 changes: 209 additions & 88 deletions Singular.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,154 @@
import {NativeEventEmitter, NativeModules, Platform} from 'react-native';
import {version} from './package.json';

const {SingularBridge} = NativeModules;
import {NativeEventEmitter, NativeModules, Platform, TurboModuleRegistry} from 'react-native';
import packageJson from './package.json';

const SDK_NAME = 'ReactNative';
const SDK_VERSION = version;
const SDK_VERSION = packageJson.version;
const ADMON_REVENUE_EVENT_NAME = '__ADMON_USER_LEVEL_REVENUE__';

export class Singular {
let SingularBridge;
let isNewArch = false;

const isNewArchitectureEnabled = global?.nativeFabricUIManager != null

try {
if (isNewArchitectureEnabled) {
const turboModule = TurboModuleRegistry?.get?.('SingularBridge');
if (turboModule) {
SingularBridge = turboModule;
isNewArch = true;
__DEV__ && console.log('[Singular SDK] Using New Architecture (TurboModule)');
} else {
throw new Error('TurboModule not available');
}
} else {
throw new Error('New Architecture not enabled');
}
} catch (error) {
SingularBridge = NativeModules.SingularBridge;
__DEV__ && console.log('[Singular SDK] Using Old Architecture (Legacy Bridge)');
}

export class Singular {
static _singularNativeEmitter = new NativeEventEmitter(SingularBridge);

static init(singularConfig) {
if (!singularConfig) {
__DEV__ && console.log('[Singular SDK] singularConfig must be a non-null object');
return;
}

const isPlainObject =
typeof singularConfig === 'object' &&
(!singularConfig.constructor || singularConfig.constructor.name === 'Object');

const handlerProperties = [
'singularLinkHandler',
'conversionValueUpdatedHandler',
'conversionValuesUpdatedHandler',
'deviceAttributionCallbackHandler',
'didSetSdidCallback',
'sdidReceivedCallback',
];

if (singularConfig.singularLinkHandler && !this._singularLinkHandler) {
this._singularLinkHandler = singularConfig.singularLinkHandler;
}
if (singularConfig.conversionValueUpdatedHandler && !this._conversionValueUpdatedHandler) {
this._conversionValueUpdatedHandler = singularConfig.conversionValueUpdatedHandler;
}
if (singularConfig.conversionValuesUpdatedHandler && !this._conversionValuesUpdatedHandler) {
this._conversionValuesUpdatedHandler = singularConfig.conversionValuesUpdatedHandler;
}
if (singularConfig.deviceAttributionCallbackHandler && !this._deviceAttributionCallbackHandler) {
this._deviceAttributionCallbackHandler = singularConfig.deviceAttributionCallbackHandler;

}
if (singularConfig.didSetSdidCallback && !this._didSetSdidCallback) {
this._didSetSdidCallback = singularConfig.didSetSdidCallback;
}
if (singularConfig.sdidReceivedCallback && !this._sdidReceivedCallback) {
this._sdidReceivedCallback = singularConfig.sdidReceivedCallback;
}

// register callback listeners
this.registerListeners();

const configWithoutHandlers = Object.keys(singularConfig)
.filter(
(key) =>
!handlerProperties.includes(key) && singularConfig[key] !== undefined
)
.reduce((obj, key) => {
obj[key] = singularConfig[key];
return obj;
}, {});

if (isNewArch) {
// If it's not a plain object (e.g., SingularConfig instance), serialize it
// Otherwise pass the plain object directly
var configForTurbo = null;
if (isPlainObject) {
configForTurbo = configWithoutHandlers;
} else {
configForTurbo = JSON.parse(JSON.stringify(configWithoutHandlers));
}

SingularBridge.init(configForTurbo);
return;
}

SingularBridge.init(JSON.stringify(singularConfig));
SingularBridge.setReactSDKVersion(SDK_NAME, SDK_VERSION);
}

static registerListeners() {
if (this._singularLinkHandler) {
this._singularNativeEmitter.addListener('SingularLinkHandler',
(params) => {
this._singularLinkHandler?.(params);
});
}

if (this._deviceAttributionCallbackHandler) {
this._singularNativeEmitter.addListener('DeviceAttributionCallbackHandler',
(attributes) => this._deviceAttributionCallbackHandler?.(attributes));
}

if (this._conversionValueUpdatedHandler) {
this._singularNativeEmitter.addListener('ConversionValueUpdatedHandler',
(conversionValue) => this._conversionValueUpdatedHandler?.(conversionValue));
}

if (this._conversionValuesUpdatedHandler) {
this._singularNativeEmitter.addListener('ConversionValuesUpdatedHandler',
(updatedConversionValues) => this._conversionValuesUpdatedHandler?.(updatedConversionValues));
}

this._singularNativeEmitter.addListener(
'SingularLinkHandler',
singularLinkParams => {
if (this._singularLinkHandler) {
this._singularLinkHandler(singularLinkParams);
}
});

this._singularNativeEmitter.addListener(
'ConversionValueUpdatedHandler',
conversionValue => {
if (this._conversionValueUpdatedHandler) {
this._conversionValueUpdatedHandler(conversionValue);
}
});

this._singularNativeEmitter.addListener(
'ConversionValuesUpdatedHandler',
updatedConversionValues => {
if (this._conversionValuesUpdatedHandler) {
this._conversionValuesUpdatedHandler(updatedConversionValues);
}
});

this._singularNativeEmitter.addListener(
'DeviceAttributionCallbackHandler',
attributes => {
if (this._deviceAttributionCallbackHandler) {
this._deviceAttributionCallbackHandler(attributes);
}
});

this._singularNativeEmitter.addListener(
'SdidReceivedCallback',
result => {
if (this._sdidReceivedCallback) {
this._sdidReceivedCallback(result);
}
});

this._singularNativeEmitter.addListener(
'DidSetSdidCallback',
result => {
if (this._didSetSdidCallback) {
this._didSetSdidCallback(result);
}
});

SingularBridge.init(JSON.stringify(singularConfig));
SingularBridge.setReactSDKVersion(SDK_NAME, SDK_VERSION);
}

static createReferrerShortLink(baseLink, referrerName, referrerId, passthroughParams, completionHandler){
let eventSubscription = this._singularNativeEmitter.addListener(
'ShortLinkHandler',
(res) => {
eventSubscription.remove();
if (completionHandler) {
completionHandler(res.data, res.error && res.error.length ? res.error: undefined);
}
});
SingularBridge.createReferrerShortLink(baseLink, referrerName, referrerId, JSON.stringify(passthroughParams));
}
if (this._didSetSdidCallback) {
this._singularNativeEmitter.addListener('DidSetSdidCallback',
(result) => this._didSetSdidCallback?.(result));
}

if (this._sdidReceivedCallback) {
this._singularNativeEmitter.addListener('SdidReceivedCallback',
(result) => this._sdidReceivedCallback?.(result));
}
}

static createReferrerShortLink(baseLink, referrerName, referrerId, passthroughParams, completionHandler) {
if (isNewArch) {
SingularBridge.createReferrerShortLink(baseLink, referrerName, referrerId, passthroughParams, completionHandler);
} else {
const subscription = this._singularNativeEmitter.addListener(
'ShortLinkHandler',
(res) => {
subscription.remove();
if (completionHandler) {
completionHandler(res.data, res.error && res.error.length ? res.error: undefined);
}
});
SingularBridge.createReferrerShortLink(baseLink, referrerName, referrerId, JSON.stringify(passthroughParams));
}
}

static setCustomUserId(customUserId) {
SingularBridge.setCustomUserId(customUserId);
Expand All @@ -101,40 +167,75 @@ export class Singular {
}

static eventWithArgs(eventName, args) {
SingularBridge.eventWithArgs(eventName, JSON.stringify(args));
if (isNewArch) {
SingularBridge.eventWithArgs(eventName, args);
} else {
SingularBridge.eventWithArgs(eventName, JSON.stringify(args));
}
}

static revenue(currency, amount) {
SingularBridge.revenue(currency, amount);
}

static revenueWithArgs(currency, amount, args) {
SingularBridge.revenueWithArgs(currency, amount, JSON.stringify(args));
if (isNewArch) {
SingularBridge.revenueWithArgs(currency, amount, args);
} else {
SingularBridge.revenueWithArgs(currency, amount, JSON.stringify(args));
}
}

static customRevenue(eventName, currency, amount) {
SingularBridge.customRevenue(eventName, currency, amount);
}

static customRevenueWithArgs(eventName, currency, amount, args) {
SingularBridge.customRevenueWithArgs(
eventName,
currency,
amount,
JSON.stringify(args),
);
if (isNewArch) {
SingularBridge.customRevenueWithArgs(eventName, currency, amount, args);
} else {
SingularBridge.customRevenueWithArgs(eventName, currency, amount, JSON.stringify(args));
}
}

// This method will report revenue to Singular and will perform receipt validation (if enabled) on our backend.
// The purchase object should be of SingularIOSPurchase / SingularAndroidPurchase type.
static inAppPurchase(eventName, purchase) {
this.eventWithArgs(eventName, purchase.getPurchaseValues());
if (isNewArch) {
if (purchase && typeof purchase.toSpecObject === 'function') {
SingularBridge.inAppPurchase(eventName, purchase.toSpecObject());
} else {
const convertedPurchase = {
revenue: purchase.r || purchase.revenue,
currency: purchase.pcc || purchase.currency,
productId: purchase.pk || purchase.productId,
transactionId: purchase.pti || purchase.transactionId,
receipt: purchase.ptr || purchase.receipt,
receipt_signature: purchase.receipt_signature
};
SingularBridge.inAppPurchase(eventName, convertedPurchase);
}
} else {
this.eventWithArgs(eventName, purchase.getPurchaseValues());
}
}

// This method will report revenue to Singular and will perform receipt validation (if enabled) on our backend.
// The purchase object should be of SingularIOSPurchase / SingularAndroidPurchase type.
static inAppPurchaseWithArgs(eventName, purchase, args) {
this.eventWithArgs(eventName, {...purchase.getPurchaseValues(), args});
if (isNewArch) {
if (purchase && typeof purchase.toSpecObject === 'function') {
SingularBridge.inAppPurchaseWithArgs(eventName, purchase.toSpecObject(), args);
} else {
const convertedPurchase = {
revenue: purchase.r || purchase.revenue,
currency: purchase.pcc || purchase.currency,
productId: purchase.pk || purchase.productId,
transactionId: purchase.pti || purchase.transactionId,
receipt: purchase.ptr || purchase.receipt,
receipt_signature: purchase.receipt_signature
};
SingularBridge.inAppPurchaseWithArgs(eventName, convertedPurchase, args);
}
} else {
this.eventWithArgs(eventName, {...purchase.getPurchaseValues(), ...args});
}
}

static setUninstallToken(token) {
Expand Down Expand Up @@ -176,7 +277,7 @@ export class Singular {
}
return true
}

static skanUpdateConversionValues(conversionValue, coarse, lock) {
if (Platform.OS === 'ios') {
SingularBridge.skanUpdateConversionValues(conversionValue, coarse, lock);
Expand All @@ -197,10 +298,28 @@ export class Singular {
}

static adRevenue(adData) {
if (!adData || !adData.hasRequiredParams()) {
return;
if (!adData) {
return;
}

if (typeof adData.hasRequiredParams === 'function') {
if (!adData.hasRequiredParams()) {
return;
}
}

if (typeof adData.hasRequiredParams !== 'function') {
const hasRequiredParams = adData.ad_platform && adData.ad_currency && adData.ad_revenue;
if (!hasRequiredParams) {
return;
}
}

if (isNewArch) {
SingularBridge.adRevenue(adData);
} else {
this.eventWithArgs(ADMON_REVENUE_EVENT_NAME, adData);
}
this.eventWithArgs(ADMON_REVENUE_EVENT_NAME, adData);
}

static setGlobalProperty(key, value,overrideExisting) {
Expand Down Expand Up @@ -229,3 +348,5 @@ export class Singular {
SingularBridge.setLimitAdvertisingIdentifiers(enabled);
}
}

export { SingularBridge };
Loading