دمج React Native SDK
غلاف React Native يربط SDK الأصلي لـ iOS وAndroid، ويمنحك إمكانات المنصتين كاملة من خلال API واحدة في TypeScript.
الخصائص
- Native Bridge: يغلف SDK الأصلي لـ iOS، Swift، وAndroid، Kotlin.
- تتبع الأحداث: تتبع الأحداث المخصصة ومشاهدات الشاشات.
- هوية المستخدم: تعريف المستخدمين وربط المعرفات وإدارة السمات.
- إشعارات Push: تسجيل الرموز وتتبع النقرات، FCM وAPNs.
- رسائل In-app: استقبال رسائل داخل التطبيق وعرضها.
- E-Commerce: تتبع المشتريات وأحداث السلة وإتمام الشراء.
- دعم العمل دون اتصال: توضع الأحداث في طابور محليًا وتُزامن عند الاتصال.
- إعادة المحاولة التلقائية: تراجع أسي عند فشل الشبكة.
المتطلبات
- React Native 0.72+.
- iOS 14.0+ / Android SDK 24+.
- TypeScript 5.0+، موصى به.
التثبيت
npm install @joryio/react-native-sdk
إعداد iOS
cd ios && pod install
إعداد Android
أضف حزمة Joryio إلى MainApplication.kt:
import io.joryio.reactnative.JoryioPackage
override fun getPackages() = PackageList(this).packages.apply {
add(JoryioPackage())
}
بدء سريع
import Joryio from '@joryio/react-native-sdk';
// Initialize once in App.tsx
await Joryio.initialize(
'jry_sdk_ios_your_key', // Your SDK key
'api-eu1.joryio.com', // API host (bare hostname, no scheme)
{
enableDebug: __DEV__,
trackSessionStart: true,
}
);
تتبع الأحداث
تتبع أحداث مخصصة
// Basic event
Joryio.track('Button Clicked');
// Event with properties
Joryio.track('Product Added', {
productId: 'SKU-123',
productName: 'Blue T-Shirt',
price: 29.99,
currency: 'USD',
});
// E-commerce events
Joryio.track('Checkout Started', {
value: 89.97,
items: [
{ productId: 'SKU-123', quantity: 2, price: 29.99 },
{ productId: 'SKU-456', quantity: 1, price: 29.99 },
],
});
Joryio.track('Order Completed', {
order_id: 'ORD-789',
value: 89.97,
currency: 'USD',
});
تتبع مشاهدات الشاشات
// In your screen components
Joryio.trackScreen('ProductDetail', { productId: 'SKU-123' });
Joryio.trackScreen('Cart');
Joryio.trackScreen('Checkout');
دمج React Navigation
import { NavigationContainer } from '@react-navigation/native';
function App() {
const routeNameRef = useRef<string>();
return (
<NavigationContainer
onStateChange={() => {
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name;
if (currentRouteName && currentRouteName !== routeNameRef.current) {
Joryio.trackScreen(currentRouteName);
routeNameRef.current = currentRouteName;
}
}}
>
{/* ... */}
</NavigationContainer>
);
}
هوية المستخدم
تعريف المستخدمين
استدع identify بعد تسجيل الدخول أو حين تعرف هوية المستخدم:
// After login
Joryio.identify('user-123');
// With attributes
Joryio.identify('user-123');
Joryio.setAttributes({
email: 'john@example.com',
firstName: 'John',
plan: 'premium',
});
ربط المستخدمين
اربط النشاط المجهول بمستخدم معروف، مثلًا بعد التسجيل:
Joryio.alias('user-123');
إعادة الضبط عند تسجيل الخروج
امسح هوية المستخدم وابدأ جلسة مجهولة جديدة:
Joryio.reset();
سمات المستخدم
// Set multiple attributes
Joryio.setAttributes({
firstName: 'John',
lastName: 'Doe',
plan: 'premium',
age: 28,
isVIP: true,
});
// Set a single attribute
Joryio.setAttribute('favoriteColor', 'blue');
// Increment a numeric attribute
Joryio.incrementAttribute('loginCount', 1);
Joryio.incrementAttribute('totalSpent', 29.99);
// Remove an attribute
Joryio.unsetAttribute('temporaryFlag');
إشعارات Push
الإعداد باستخدام Firebase، React Native Firebase
import messaging from '@react-native-firebase/messaging';
// Request permission
const authStatus = await messaging().requestPermission();
// Get and register token
const token = await messaging().getToken();
Joryio.registerPushToken(token);
// Listen for token refresh
messaging().onTokenRefresh((newToken) => {
Joryio.registerPushToken(newToken);
});
معالجة نقرات إشعارات Push
import messaging from '@react-native-firebase/messaging';
// When app is in background and notification is tapped
messaging().onNotificationOpenedApp((remoteMessage) => {
const trackingId = remoteMessage.data?.joryio_tracking_id;
if (trackingId) {
Joryio.trackPushClick(trackingId);
}
});
// When app was killed and opened via notification
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage?.data?.joryio_tracking_id) {
Joryio.trackPushClick(remoteMessage.data.joryio_tracking_id);
}
});
فحص حالة Push
const enabled = await Joryio.isPushEnabled();
console.log('Push enabled:', enabled);
رسائل In-app
تُعرض الرسائل من تلقاء نفسها. ثبّت الحزمة وأرسل حملة فتظهر - ترسمها العناصر الأصلية بخطوط تطبيقك وألوانه ووضعه الداكن. لا شيء لتوصيله.
لن تحتاج بقية هذا القسم إلا إذا أردت عرض الرسائل داخل React بدلًا من ذلك؛ راجع تولي العرض بنفسك.
رموز التسليم (delivery tokens)
عندما يقدّم الخادم حملة مؤهّلة، فإنه يصدر لها أيضًا رمز تسليم موقّعًا وقصير الأجل. يعيد الـ SDK إرسال هذا الرمز عند الإبلاغ عن ظهور أو نقرة أو إغلاق، ويتحقق الخادم من التوقيع قبل تسجيل أي شيء.
لا يتعيّن عليك فعل أي شيء - الـ SDK يتولى ذلك نيابة عنك. وهو موثّق هنا لأنه يغيّر ما يحدث للعميل الذي لا يرسل رمزًا:
POST /v1/in-app/track (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }
الرمز هو ما يجعل الظهور جديرًا بالثقة: بدونه، يستطيع أي شخص يملك مفتاح الـ SDK - وهو مضمّن في كل تطبيق وكل صفحة - الإبلاغ عن ظهورات ونقرات لحملة لم تُعرض قط، وستحتسبها تقاريرك.
إذا توقّف تسجيل ظهورات in-app، فتأكد من أن التطبيق مبني على إصدار حديث من الـ SDK: الإصدار الذي بُني قبل وجود رموز التسليم لا يرسل رمزًا، وسيرفض الخادم ظهوراته.
نوعان من المحتوى
تحمل كل رسالة الحقل kind الذي يحدد شكلها. تفرّع بناءً عليه:
kind | ما يحمله | كيفية عرضه |
|---|---|---|
'native' | title وbody وimageUrl وbuttons | مكوّنات React Native - <Text> و<Image> و<Pressable> |
'html' | html وcss | WebView |
تولي العرض بنفسك
الاشتراك عبر onInAppMessage يوقف رسم الحزمة ويسلّم كل رسالة إلى شيفرتك
لتعرضها بمكوّنات React Native. ولن تحصل على نسختين.
افعل ذلك إن أردت أن تطابق الرسائل بقية واجهتك، أو إذا كان ممنوعًا على التطبيق ربط
web view - وعندها استبعد أيضًا artifact الواجهة من البناء (joryio-android-ui
على أندرويد، والمنتج JoryioUI على iOS).
الاستماع للرسائل
import { useEffect, useState } from 'react';
import Joryio, { type InAppMessage } from '@joryio/react-native';
function App() {
const [message, setMessage] = useState<InAppMessage | null>(null);
useEffect(() => Joryio.onInAppMessage(setMessage), []);
return <>{message && <InAppMessageHost message={message} />}</>;
}
عرض رسالة أصلية
المحتوى الأصلي نص وليس markup. ضعه داخل <Text> - فتمريره إلى WebView أو إلى dangerouslySetInnerHTML يعيد خطر الحقن ذاته الذي يتجنبه المسار الأصلي.
function InAppMessageHost({ message }: { message: InAppMessage }) {
if (message.kind === 'native') {
return (
<View>
{message.imageUrl && <Image source={{ uri: message.imageUrl }} />}
{message.title && <Text style={styles.title}>{message.title}</Text>}
<Text>{message.body}</Text>
{message.buttons.map((button) => (
<Pressable
key={button.id}
onPress={() => {
if (button.action === 'url' && button.url) Linking.openURL(button.url);
Joryio.trackInAppImpression(message.id, 'clicked');
}}
>
<Text>{button.text}</Text>
</Pressable>
))}
</View>
);
}
return <WebView source={{ html: `<style>${message.css}</style>${message.html}` }} />;
}
طبِّق تنسيق الحملة
يحمل message.style التجاوزات التي ضبطها محرّر الحملة. كل حقل اختياري، والحقل
الغائب يعني وراثة مظهر تطبيقك - فطبِّق الموجود فقط دون بدائل من عندك:
const st = message.style;
const text = {
// 'auto' يحاذي حسب لغة الرسالة لا لغة الجهاز.
writingDirection: 'auto' as const,
textAlign: st?.textAlign === 'center' ? 'center' : 'auto',
...(st?.fontFamily ? { fontFamily: st.fontFamily } : {}),
};
طبِّق fontFamily فقط إن كان تطبيقك يرفق ذلك الخط. وإن ضبطت الحملة لون زر دون
لون نص، فاختر الأسود أو الأبيض حسب التباين - فالأبيض الافتراضي يُخفي لونًا فاتحًا.
السماح برسائل HTML
رسائل HTML معطلة افتراضيًا. تُنفذ رسالة HTML شيفرة JavaScript كتبها المؤلف داخل تطبيقك، لذا فإن تفعيلها قرار يتخذه فريق التطبيق:
await Joryio.initialize('jry_sdk_YOUR_KEY', 'api-eu1.joryio.com', {
allowHtmlJsInAppMessages: true, // الافتراضي: false
});
تركه معطلًا لا يعطل الرسائل داخل التطبيق - تستمر الرسائل الأصلية في الوصول. تُتجاهل حملات HTML فقط.
تتبع مرات الظهور
// When message is displayed
Joryio.trackInAppImpression(message.id, 'displayed');
// When user clicks
Joryio.trackInAppImpression(message.id, 'clicked');
// When user dismisses
Joryio.trackInAppImpression(message.id, 'dismissed');
خيارات الإعداد
| الخيار | النوع | الافتراضي | الوصف |
|---|---|---|---|
enableDebug | boolean | false | تفعيل سجلات التصحيح |
logLevel | string | 'info' | مستوى السجل: debug أو info أو warn أو error |
batchSize | number | 50 | الأحداث في كل دفعة قبل التفريغ التلقائي، الافتراضي الأصلي |
flushInterval | number | 5000 | فاصل التفريغ التلقائي بالـms، الافتراضي الأصلي |
sessionTimeout | number | 1800000 | مهلة الجلسة بالـms، 30 دقيقة افتراضيًا |
trackSessionStart | boolean | true | تتبع أحداث بدء الجلسة تلقائيًا |
userId | string | null | User ID مضبوط مسبقًا عند التهيئة |
أدوات مساعدة
// Flush events immediately (before app close, logout, etc.)
Joryio.flush();
// Get IDs
const anonymousId = await Joryio.getAnonymousId();
const userId = await Joryio.getUserId(); // null if not identified
const sessionId = await Joryio.getSessionId();
دعم TypeScript
SDK مكتوب بالكامل بالأنواع. استورد الأنواع عند الحاجة:
import Joryio, {
type JoryioConfig,
type UserAttributes,
type InAppMessage,
} from '@joryio/react-native-sdk';