שילוב iOS SDK
iOS SDK נייטיבי למעקב אירועים, ניהול סשנים, שליחת התראות פוש והצגת הודעות בתוך האפליקציה.
יכולות
- קל משקל - טביעת רגל מינימלית
- מהיר - מותאם לביצועים
- תמיכה במצב לא מקוון - תור אירועים מבוסס SQLite
- ניסיונות חוזרים - השהיה מעריכית בין ניסיונות לאחר כישלון
- שליחה באצוות - שליחה יעילה באצוות (50 אירועים / 5 שניות)
- In-App Messaging - הודעות Native ו־HTML, 5 סוגים, עם הגבלת תדירות
- Push Notifications - Apple Push Notification Service (APNS)
- גישה ממוקדת פרטיות - תואם GDPR ומכבד הסכמת משתמש
- iOS 14+ - תמיכה בגרסאות iOS מודרניות
דרישות
- iOS 14.0+
- Xcode 15.0+
- Swift 5.9+
ה-API של ה-SDK הוא Swift בלבד, ולכן לא ניתן לקרוא לו ישירות מקובץ .m או .mm.
אפליקציית Objective-C משלבת אותו על ידי הוספת קובץ Swift אחד שמייבא את
Joryio וחושף את הקריאות שהאפליקציה צריכה, ואז קוראים לו מ-Objective-C כרגיל.
Xcode מגדיר את ה-bridging header אוטומטית בפעם הראשונה שמוסיפים קובץ Swift
למטרת Objective-C.
התקנה
Swift Package Manager (מומלץ)
ה־SDK מופץ כחבילת Swift מ־https://github.com/joryio/joryio-ios-sdk. זו הדרך המומלצת להתקין אותו; CocoaPods הוצא משימוש (ראו CocoaPods). הוסיפו את השורה הבאה ל־Package.swift:
dependencies: [
.package(url: "https://github.com/joryio/joryio-ios-sdk.git", from: "1.3.3")
]
החבילה מספקת שלושה products. קשרו את שני הראשונים לאפליקציה לפי הצורך; השלישי מיועד רק ל־Notification Service Extension (ראו תמונות בהתראות פוש):
.target(
name: "YourApp",
dependencies: [
.product(name: "Joryio", package: "joryio-ios-sdk"), // מעקב, זהות, פוש
.product(name: "JoryioUI", package: "joryio-ios-sdk"), // + הצגת הודעות In-App
]
)
או ב־Xcode:
- File → Add Package Dependencies
- הזינו:
https://github.com/joryio/joryio-ios-sdk.git - בחרו גרסה והוסיפו ל־target
CocoaPods (הוצא משימוש)
CocoaPods הוצא משימוש עבור ה־SDK של Joryio ל־iOS, לטובת
Swift Package Manager. CocoaPods trunk, המאגר ששורת
pod 'Joryio' רגילה מורידה ממנו, נסגר (קריאה בלבד מ־2 בדצמבר 2026), ולכן אין
ערובה שגרסאות חדשות יופיעו בו. אם האפליקציה שלכם חייבת להישאר ב־CocoaPods,
התקינו מה־tag של ה־SDK ב־git כמו בדוגמה למטה: זה לא עובר דרך trunk, וכל גרסה
מ־1.3.0 ואילך תומכת בכך, כולל גרסאות בטא. קבעו את הגרסה עם :tag ושנו את ה־tag
כדי לשדרג - pod update לא מזיז אותו.
כדי לעבור ל־Swift Package Manager: מחקו את שורות pod 'Joryio...' מה־Podfile,
הריצו pod install, והוסיפו את החבילה כמתואר למעלה.
שום דבר אחר בקוד לא משתנה - פרט לכך ש־Notification Service Extension כותב
import JoryioNotificationServiceExtension במקום import Joryio.
pod 'Joryio/UI', :git => 'https://github.com/joryio/joryio-ios-sdk.git', :tag => '1.3.3' # הכול, כולל הצגת In-App
# pod 'Joryio', :git => 'https://github.com/joryio/joryio-ios-sdk.git', :tag => '1.3.3' # מעקב, זהות ופוש בלבד - ללא WebKit מקושר
# רק ב-target של Notification Service Extension:
# pod 'Joryio/NotificationServiceExtension', :git => 'https://github.com/joryio/joryio-ios-sdk.git', :tag => '1.3.3'
JoryioUI תלוי ב־Joryio, כך שקישור ה־product של ה־UI מביא את שניהם.
קחו את Joryio לבדו כשלאפליקציה אין שימוש בהודעות In-App, או כשסקירת אבטחה
מתנגדת לקישור WebView. שימו לב שברירת המחדל ב־CocoaPods היא Joryio נטול ה־UI -
הפקודה pod 'Joryio' לא תציג הודעות In-App עד שתחליפו אותה ב־pod 'Joryio/UI'.
מניפסט פרטיות
ה־SDK מגיע עם מניפסט הפרטיות של Apple (PrivacyInfo.xcprivacy) גם בחבילת Swift וגם ב־pod של CocoaPods. הוא מצהיר על ה־API היחיד מסוג required-reason שה־SDK משתמש בו - UserDefaults, סיבה CA92.1 - ועל הנתונים שה־SDK אוסף: מזהה משתמש, מזהה מכשיר, אינטראקציה עם המוצר והיסטוריית רכישות, כל אחד מהם מקושר למשתמש ואינו משמש למעקב (tracking). כולם מוצהרים למטרות App Functionality, Analytics ו־Developer's Advertising or Marketing, וכולם מלבד מזהה המכשיר גם ל־Product Personalization. לא מוצהר מיקום: ה־SDK לעולם אינו קורא את מיקום המכשיר, והמדינה שה־backend של Joryio גוזר מכתובת ה־IP אינה נתון שה־SDK אוסף. Xcode כולל אותו בדוח הפרטיות של האפליקציה שלכם. מאפיינים ומאפייני אירועים שאתם בוחרים לשלוח - עליכם להצהיר עליהם במניפסט של האפליקציה שלכם.
התחלה מהירה
1. אתחול ה־SDK
ב־AppDelegate.swift:
import Joryio
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize with your SDK key
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_SDK_KEY",
apiHost: "https://api-eu1.joryio.com/api"
)
return true
}
מצאו את מפתח ה־SDK בלוח הבקרה של Joryio תחת Settings → Apps → [האפליקציה שלכם] → SDK Keys.
בטוח לקרוא לכל מתודה ציבורית לפני initialize(). הקריאות optOut(), optIn(), wipeData(), updateConsent(_:), setSdkAuthenticationToken(_:), setSdkAuthenticationErrorHandler(_:) ו־setInAppMessageCallback(_:capabilities:) נשמרות ומוחלות כשה־SDK עולה. קריאות שאוספות או שולחות - track, identify, מאפיינים, addAlias, מנויים, reset, flush, קריאות אסימון הפוש, אישור המסירה, ההקלקה וביטול הרישום, ודיווחי In-App - נכנסות לתור (עד 1000, הוותיקה ביותר נזרקת ראשונה, אותה מגבלה בכל ה־SDKs של Joryio) ורצות לפי הסדר אחרי ש־initialize() מצליח. עד אז מתודות הקריאה מחזירות ערכים ריקים. קריאה שאוספת נתונים - אירוע, מאפיין, alias, הרשמה למנוי, אישור מסירה או הקלקה של פוש, דיווח In-App - שנעשית בזמן שהמשתמש ביטל הסכמה למעקב (opt-out) או שההסכמה נדחתה, נזרקת מיד: optIn() או .granted מאוחרים יותר לעולם לא ישלחו אותה. identify עדיין נשמר, כדי שבקשות ביטול יישאו את המשתמש הנכון. החלטות שמתקבלות לפני initialize() - optOut(), optIn(), updateConsent(_:), wipeData() - נכנסות לתוקף לפני כל דבר שה־SDK עושה בעלייה (Session Start, סנכרון In-App, רישום פוש מחדש). optOut() / optIn() שנקראו לפני initialize() גוברים על optOut בתצורה. isUserOptedOut() ו־getTrackingConsent() עונים לפי מה שנשמר גם לפני initialize(). כשעדיין לא התקבלה שום החלטה, getTrackingConsent() עונה .granted - ההסכמה שבה מתחילה אפליקציה בלי באנר הסכמה. אם initialize() דוחה את מפתח ה־SDK או את ה־host, registerPushToken(_:onResult:) עונה מיד success: false במקום להמתין.
עלייה לפני פתיחת הנעילה הראשונה. רק עלייה לפני שהמכשיר נפתח פעם אחת מאז שהודלק (פוש ברקע או התעוררות מיקום מיד אחרי אתחול) ממתינה: עד אז אי אפשר לקרוא את האחסון של ה־SDK. initialize() מסתיים מעצמו בפתיחה הראשונה הזו, קריאות שנעשו בינתיים נשארות בתור, ו־isInitializationPending הוא true בזמן ההמתנה - זה אינו כשל. מכשיר שפשוט נעול, ושכבר נפתח פעם אחת מאז שהודלק, עולה מיד: פוש שקט בטלפון נעול מטופל כרגיל.
2. מעקב אירועים
// Basic event
Joryio.shared.track("Button Tapped")
// Event with properties
Joryio.shared.track("Product Viewed", properties: [
"product_id": "abc123",
"product_name": "Wireless Headphones",
"price": 99.99,
"category": "Electronics"
])
// Screen view
Joryio.shared.trackScreen("ProductDetail", properties: [
"product_id": "abc123"
])
3. זיהוי משתמשים
שלחו תמיד מספרי טלפון בפורמט בינלאומי מלא (E.164): +, קידומת המדינה ואז המספר - למשל +972501234567. מספר בלי + נקרא לפי מדינת ברירת המחדל למספרי טלפון של סביבת העבודה; אם לא הוגדרה, או שאפשר לקרוא את הספרות בשתי דרכים, הוא נשמר כלא מאומת ולא משמש להודעות. איך מטופלים מספרי טלפון
// Identify a user
Joryio.shared.identify("user-123")
Joryio.shared.setAttributes([
"name": "John Doe",
"plan": "premium"
])
// Set attributes later
Joryio.shared.setAttribute("last_purchase", value: Date())
Joryio.shared.incrementAttribute("lifetime_value", by: 99.99)
// A second identifier for the same person - works before identify() too
Joryio.shared.addAlias("loyalty_id", name: "L-12345")
// On logout
Joryio.shared.reset()
identify מצרף בשרת את ההיסטוריה האנונימית של המכשיר למשתמש, רושם מחדש את אסימון הפוש תחתיו ומסנכרן מחדש את הודעות ה־In-App.
זיהוי משתמש אחר בלי reset() ביניהם מטופל כמו התנתקות והתחברות לגבי מה שהמשתמש הקודם השאיר: כתיבות המאפיינים שלו שלא נשלחו יוצאות תחת המזהים שלו, לעולם לא תחת המשתמש החדש, ועם אימות SDK הטוקן שלו נשאר קשור לפריטים שלו בתור. כל עוד האדם בחר שלא להיות במעקב או שההסכמה נדחתה, identify רק שומר את מזהה המשתמש במכשיר, כדי שביטול מנוי, רשומת ה־opt-out או ביטול רישום פוש יישאו את המשתמש הנכון - שום דבר לא נשלח, לא נרשם במעקב ולא נכתב לפרופיל. ה־identify הרגיל רץ כשהמעקב מותר שוב; reset() ו־wipeData() שוכחים את המזהה השמור.
reset מתחיל זהות אנונימית חדשה מיד: מזהה אנונימי חדש, ללא מזהה משתמש, ללא כתיבות מאפיינים ממתינות וללא מצב In-App, וסשן חדש שמוכרז באירוע Session Start. קריאת identify בשורה הבאה ממש כבר משתמשת בזהות החדשה. אסימון הפוש נרשם מחדש תחתיה והודעות ה־In-App מסתנכרנות מחדש. אירועים שנכנסו לתור לפני האיפוס עדיין נשלחים, עם המזהים שתחתם נרשמו.
קריאה שחסר בה ערך חובה - identify(""), track(""), מפתח מאפיין ריק - רושמת אזהרה ולא עושה דבר. מספר שאינו סופי (NaN או אינסוף) במאפייני אירוע או בערכי מאפיינים נזרק עם אזהרה ביומן, ושאר הקריאה ממשיכה.
תוויות של addAlias משתמשות באותיות לטיניות קטנות, ספרות וקווים תחתונים. כינוי (alias) שנוסף למבקר אנונימי נשאר בפרופיל שלו כשהוא מזוהה.
היכן נשמרים המזהים
| ערך | נשמר ב־ | אחרי מחיקת האפליקציה |
|---|---|---|
| מזהה אנונימי, מזהה מכשיר, סשן | UserDefaults | נמחק |
opt-out, דחיית הסכמה שמורה, החלטות שהתקבלו לפני initialize, היסטוריית הצגת הודעות In-App | UserDefaults | נמחק |
| מזהה משתמש, אסימון מכשיר APNs, כתיבות מאפיינים שלא נשלחו, ביטולי רישום פוש ממתינים (הם נושאים את האסימון), מזהה משתמש שנשמר בזמן opt-out, טוקן אימות SDK של משתמש שהתנתק (עד שהפריטים שלו בתור נשלחים או נזרקים) | Keychain (במכשיר הזה בלבד, אחרי פתיחת הנעילה הראשונה) | נמחק - ה־SDK מזהה התקנה מחדש ומנקה את הפריטים שלו ב־Keychain |
אירועים בתור; כתיבות identify, alias, מנוי ו־opt-out שממתינות לשרת; כתיבות מאפיינים שהשאיר משתמש קודם כשהאפליקציה זיהתה מישהו אחר בלי reset() | SQLite ב־Application Support, מוחרג מגיבויים | נמחק |
כל מפתח שה־SDK כותב ל־UserDefaults מתחיל ב־jry_ או ב־joryio_.
נתוני סשן אוטומטיים
iOS SDK מעשיר אירועי Session Start בנתוני מכשיר וסביבה:
$device_id$platform(ios)$model$os_name/$os_version$app_version/$build_number$bundle_id$screen_width/$screen_height$locale$language/$languages$timezonecountry(ISO-3166-1 alpha-2, נגזר מה־IP בתחילת סשן)
התראות פוש
בקשו את ההרשאה כשלמשתמש יש סיבה להסכים. בקשה בשניות הראשונות של הסשן היא זו שנדחית הכי הרבה, וסירוב הרבה יותר קשה לשינוי מאשר עיכוב - בקשת המערכת היא חד-פעמית, ולכן "לא" שם הוא למעשה קבוע, בעוד שבקשה מאוחרת יותר לא עולה לכם דבר.
ה-SDK לעולם לא מציג את הבקשה במקומכם. אם החלטתם שאתם כן רוצים אותה כבר בהפעלה,
הגדירו requestPushPermissionAtLaunch - היא כבויה כברירת מחדל, לא חסרה.
הפעילו התראות פוש כדי לשלוח הודעות ממוקדות דרך Apple Push Notification Service (APNS).
1. הגדרה ב־AppDelegate
import Joryio
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize SDK
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_KEY",
apiHost: "https://api-eu1.joryio.com/api"
)
// Request push permissions
Task {
let granted = await Joryio.shared.requestPushPermission()
if granted {
print("Push notifications enabled")
}
}
return true
}
// Handle device token registration
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
Joryio.shared.didRegisterForRemoteNotifications(deviceToken: deviceToken)
}
// Handle registration failure
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
Joryio.shared.didFailToRegisterForRemoteNotifications(error: error)
}
// Handle received push notification
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
Joryio.shared.didReceiveRemoteNotification(userInfo, completionHandler: completionHandler)
}
}
ה־SDK הופך ל־delegate של UNUserNotificationCenter ב־initialize() ושומר את ה־delegate שהאפליקציה הגדירה לפניו. הוא מטפל רק בהתראות של Joryio (מפתח joryio ב־payload) - מציג אותן בחזית ומטפל בהקשות עליהן - ומעביר כל התראה אחרת, ואת ה־callback של ההגדרות, ל־delegate שלכם ללא שינוי. אם אתם מגדירים delegate משלכם אחרי initialize(), דווחו בעצמכם על הקשות על התראות Joryio באמצעות trackPushClick(trackingId:).
didRegisterForRemoteNotifications(deviceToken:) הוא גם נקודת הכניסה למנוע שמקבל את אסימון ה־APNs בעצמו - ה־bridge של Unity מעביר אליו את האסימון מ־kUnityDidRegisterForRemoteNotificationsWithDeviceToken. אסימון שמגיע לפני initialize() נשמר ונרשם כשה־SDK עולה.
האסימון נרשם ב־Joryio רק כל עוד המשתמש לא ביטל את ההסכמה (opted in) והסכמת המעקב ניתנה. אחרת הוא נשאר במכשיר ונרשם כשהמצב משתנה. אישורי מסירה ומעקב הקלקות פועלים לפי אותו כלל.
2. הגדרת APNS בלוח הבקרה
כדי לשלוח התראות פוש, הזינו את אישורי ה־APNs באפליקציית ה־iOS ב־Joryio:
- עברו אל הגדרות → אפליקציות ולחצו על סמל התראות Push (הפעמון) בשורה של אפליקציית ה־iOS
- בחרו Environment - פיתוח (Sandbox) לגרסאות שמורצות מ־Xcode, סביבת ייצור לגרסאות App Store ו־TestFlight
- בחרו מבוסס טוקן (מפתח .p8) והזינו את ה־Key ID, ה־Team ID ואת התוכן של מפתח האימות
.p8 - לחצו על שמור הגדרות
להוראות מפורטות ליצירת מפתח האימות בחשבון Apple Developer, ראו קבלת אישורי APNs.
3. יכולות פוש
// Check if push is enabled - true exactly when the status is "granted"
let isEnabled = await Joryio.shared.isPushEnabled()
let status = await Joryio.shared.getPushPermissionStatus() // "granted", "denied" or "notDetermined"
// Get device token
if let token = Joryio.shared.getDeviceToken() {
print("Device token: \(token)")
}
// Badge management
Joryio.shared.updateBadgeCount(5)
Joryio.shared.clearBadge()
// Unregister from push - removes the token on the Joryio server and with iOS.
// Holds across launches until registerPushToken(_:) or requestPushPermission().
Joryio.shared.unregisterPush()
// Your own push stack: hand the token over and report what happened
Joryio.shared.registerPushToken(hexToken) { result in
// result.success, result.message, result.isUnauthorized, result.isRetryable
}
Joryio.shared.reportPushDelivered(trackingId)
Joryio.shared.trackPushClick(trackingId: trackingId)
הרשאה זמנית (provisional) והרשאה חולפת (ephemeral) נחשבות "granted". מאפיין המשתמש push_permission משתמש באותם ערכים, עם not_determined במקום "notDetermined".
האסימון חייב להיות אסימון המכשיר של APNs כמחרוזת hex. כל דבר אחר - למשל אסימון רישום של Firebase (FCM) - נדחה: onResult מדווח success: false וה־SDK רושם ביומן את הסיבה. unregisterPush() נזכר גם בהפעלות הבאות: האסימון ש־iOS מוסר בהפעלה מאוחרת יותר נשמר במכשיר אך אינו נרשם, עד שהאפליקציה קוראת שוב ל־registerPushToken(_:onResult:) או ל־requestPushPermission(). registerPushToken(_:onResult:) קורא ל־onResult פעם אחת עם התוצאה; תשובת 200 עם success: false מדווחת ככישלון, לעולם לא כהצלחה. reportPushDelivered(_:) ו־trackPushClick(trackingId:) מיועדות לאפליקציה שמטפלת בהתראות בעצמה, כך שאף התראה לא מגיעה למטפלים של ה־SDK.
תמונות בהתראות פוש
APNs לא יכול לצרף תמונה בעצמו; Notification Service Extension באפליקציה שלכם צריך לעשות זאת. הוסיפו אחד (File → New → Target → Notification Service Extension) וקשרו את ה־product של ה־extension ל־target הזה בלבד:
- Swift Package Manager:
JoryioNotificationServiceExtension - CocoaPods:
pod 'Joryio/NotificationServiceExtension'בבלוק ה־target של ה־extension
אל תקשרו את Joryio ל־extension: הוא קורא ל־UIApplication.shared, שאסור לשימוש ב־app extensions, והוא מכניס SQLite לתהליך שמוגבל ל־24 MB. ל־product של ה־extension אין תלויות. החליפו את המחלקה שנוצרה ב:
import JoryioNotificationServiceExtension // CocoaPods: import Joryio
class NotificationService: JoryioNotificationService {}
הוא מוריד את ה־URL שבמפתח jry_image_url של ה־payload (או attachment-url) ומצרף אותו.
הודעות בתוך האפליקציה
הציגו הודעות בתוך האפליקציה למשתמשים לפי התנהגות ומאפיינים.
אסימוני שליחה (delivery tokens)
כאשר השרת מגיש קמפיין מתאים, הוא מנפיק עבורו גם אסימון שליחה חתום וקצר-מועד. ה-SDK מחזיר את האסימון כשהוא מדווח על הצגה, קליק או סגירה, והשרת מאמת את החתימה לפני שהוא רושם משהו.
אינך צריך לעשות דבר - ה-SDK מטפל בזה עבורך. התיעוד כאן קיים כי ניתן להגדיר את השרת לדרוש את האסימון. כברירת מחדל דיווח ללא אסימון עדיין מתקבל - השרת רושם אותו ביומן כלקוח מגרסה שקדמה לאסימונים - ורק כאשר השרת רץ עם IN_APP_REQUIRE_DELIVERY_TOKEN=true אותו דיווח נדחה:
POST /v1/in-app/track (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }
אסימון שנשלח אך אינו עובר אימות - מזויף, שונה או שפג תוקפו - נדחה תמיד, ללא קשר להגדרה.
האסימון הוא מה שהופך דיווח הצגה לאמין: בלעדיו, כל מי שמחזיק במפתח ה-SDK - שנשלח בתוך כל אפליקציה ובכל עמוד - היה יכול לדווח על הצגות וקליקים לקמפיין שמעולם לא הוצג, והדוחות שלך היו סופרים אותם.
אם הצגות in-app מפסיקות להירשם מול שרת שאוכף אסימונים, ודא שהאפליקציה נבנתה מול גרסה עדכנית של ה-SDK: בנייה שנעשתה לפני שאסימוני השליחה נוספו אינה שולחת אסימון.
סנכרון קמפיינים אוטומטי
ה־SDK מסנכרן קמפיינים מהשרת אוטומטית:
- בהפעלה וכשהאפליקציה עוברת לחזית (foreground) - לכל היותר פעם אחת בכל
syncInterval(ברירת מחדל 300 שניות) - מיד כשהמשתמש מזוהה או מאופס, כשמקישים על פוש, כשמגיע פוש סנכרון שקט, וכשמשתנה מאפיין שקמפיין עוקב אחריו
syncInAppCampaigns() מסנכרן לפי דרישה ואינו מוגבל בקצב.
הודעות שמופעלות על ידי אירוע, הקשה על פוש או סף של מאפיין עוברות דרך אותן מגבלות תדירות ואותו כלל של הודעה אחת בכל פעם כמו כל הודעה אחרת.
שליטה ידנית בקמפיינים
// Manually sync campaigns from server
await Joryio.shared.syncInAppCampaigns()
// Manually trigger campaign evaluation
await Joryio.shared.evaluateInAppCampaigns()
// Reset displayed campaigns (for testing)
Joryio.shared.resetDisplayedCampaigns()
enableDebugevaluateInAppCampaigns() ו-resetDisplayedCampaigns() משנות מצב אמיתי - האחת יכולה לכפות הודעה על המסך והשנייה מוחקת תקרות תדירות אמיתיות - ולכן הן מתעלמות אלא אם ה-SDK אותחל עם enableDebug: true; בבניית release נרשמת אזהרה ולא קורה דבר. האבחונים לקריאה בלבד (getQueueSize(), getSessionInfo(), getIdentity(), getDeviceToken()) אינם חסומים. syncInAppCampaigns() הוא API רגיל ורץ תמיד.
שני סוגי תוכן
כל קמפיין מגיע באחת משתי צורות תוכן, וה־SDK מציג כל אחת מהן אחרת:
| תוכן | מה זה | איך זה מוצג |
|---|---|---|
| Native | נתונים מובנים - כותרת, טקסט, תמונה, כפתורים | View־ים אמיתיים של UIKit, עם צבע ההדגשה, Dynamic Type, המצב הכהה ו־VoiceOver של האפליקציה. ללא WebView. |
| HTML | קוד HTML ו־CSS שנכתב על ידי המחבר | WKWebView בתוך ההודעה, תחת Content-Security-Policy מחמיר. |
הפעלת הודעות HTML
הודעות HTML כבויות כברירת מחדל. הודעת HTML מציגה קוד שנכתב על ידי המחבר בתוך WebView באפליקציה שלכם, ולכן ההפעלה היא החלטה של צוות האפליקציה - לא משהו שמפעילים ממערכת השיווק:
let config = JoryioConfig(
inApp: InAppConfig(allowHtmlJsInAppMessages: true) // ברירת מחדל: false
)
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_KEY",
apiHost: "https://api-eu1.joryio.com/api",
config: config
)
השארת ההגדרה כבויה אינה מבטלת הודעות In-App. הודעות Native ימשיכו להופיע, כי הן נתונים שה־SDK מצייר עם ה־View־ים שלו - ללא מפרש כלשהו. קמפייני HTML מדולגים ונרשמים ללוג.
ההגדרה חלה על המרנדר של ה־SDK עצמו. אם האפליקציה שלכם מציירת את ההודעות בעצמה באמצעות setInAppMessageCallback(_:capabilities:), ה־WebView של ה־SDK אינו רץ כלל, ולכן ההגדרה אינה חלה על המסלול הזה - הצהירו על content.html ב־capabilities, וקמפייני HTML יועברו ל־callback שלכם בין אם ההגדרה דלוקה ובין אם לא. המרנדר שלכם, ההחלטה שלכם.
מסמך ההודעה מוצג תחת Content-Security-Policy שחוסם כל סקריפט, frames, גישה לרשת ושליחת טפסים - אותם כללים כמו ב־web SDK. רק ה־joryioBridge של ה־SDK עצמו רץ, כך שקישורים, כפתורים, מאפייני data-joryio-action, קריאות bridge וטפסי data-jry-form עובדים, אבל <script> של המחבר, מטפלי on* מוטמעים והטמעות <iframe> אינם רצים ב־iOS (וגם לא ב־web).
אם מדיניות האבטחה שלכם אוסרת הצגת HTML שנכתב חיצונית בתוך התהליך, השאירו את ההגדרה כבויה וכתבו את הקמפיינים כהודעות Native.
רינדור ההודעות בעצמכם
ה-SDK מצייר עבורכם הודעות Native, אבל אפשר לקחת שליטה מלאה - אותו פתח מילוט
שספקים אחרים קוראים לו custom view factory. ממשו את InAppMessagePresenter
והציבו אותו:
Joryio.shared.inAppPresenter = MyPresenter()
הגילוי האוטומטי רץ רק כאשר inAppPresenter הוא nil, ולכן שלכם מחליף את
הרנדרר המובנה במקום להתנגש בו. אפשר להציב לפני או אחרי initialize.
ה-presenter מקבל את הקמפיין כש-content כבר מוכן - ה-Liquid מרונדר בצד השרת,
ושדות Native מגיעים כטקסט רגיל. החזירו true מ־present(campaign:in:delegate:)
רק כשההודעה על המסך: ה־SDK מדווח אז "displayed" בעצמו וסופר את ההצגה במגבלת
התדירות של הקמפיין. דווחו על הקשות ועל סגירה דרך ה־delegate שקיבלתם -
messageViewClicked(_:action:url:) עם מזהה הכפתור או הקישור, ו־messageViewDidDismiss(_:)
כשההודעה נסגרת. אל תקראו ל־trackInAppImpression מתוך presenter: ההודעה תדווח פעמיים.
כדי לרנדר עם ממשק משלכם בלי presenter, השתמשו ב־callback במקום:
Joryio.shared.setInAppMessageCallback({ campaign in
showMyMessage(campaign)
Joryio.shared.trackInAppImpression(campaignId: campaign.id, action: "displayed")
}, capabilities: ["content.native"])
// When a button is tapped, and when the message closes:
Joryio.shared.trackInAppImpression(campaignId: campaign.id, action: "clicked", elementId: "buy_now")
Joryio.shared.trackInAppImpression(campaignId: campaign.id, action: "dismissed")
// Hand display back to the SDK's own renderer:
Joryio.shared.clearInAppMessageCallback()
action הוא "displayed", "clicked", "dismissed" או "converted"; כל ערך אחר נדחה עם הודעה ביומן. elementId מציין, אופציונלית, את הכפתור או הקישור בהקלקה. הדיווח "displayed" שלכם הוא היחיד שנשלח עבור ההודעה הזו, והוא זה שסופר את ההודעה במגבלת התדירות של הקמפיין - הודעה שהועברה ל־callback ולא הוצגה אינה צורכת ממנה דבר. "dismissed" משחרר את ה־SDK להעביר את ההודעה הבאה: הודעה שדיווחתם עליה "displayed" תופסת את התצוגה עד שתדווחו "dismissed", כך ששום דבר לא מצויר מעליה. בלי אף דיווח, ה־SDK משתחרר מעצמו אחרי 30 שניות; הודעה שדווחה "displayed" ולא דווחה "dismissed" תופסת את התצוגה לכל היותר 5 דקות. דווחו "dismissed" בכל פעם שההודעה יורדת מהמסך, גם אחרי הקלקה. הודעה שממתינה להשהיית התצוגה שלה לא מועברת אם המשתמש מזוהה כאדם אחר, בוחר שלא להיות במעקב או משנה הסכמה.
החל מגרסה 1.3.2. callback שמוגדר לפני initialize() מותקן לפני הסנכרון הראשון. אם אתם מגדירים אותו אחרי initialize(), הסנכרון הראשון יכול להסתיים לפניו וה-SDK מצייר את ההודעה הזו בעצמו; העבירו InAppConfig(holdInAppMessagesForHandler: true) כדי למנוע זאת. הודעה שמוכנה לפני שה-callback הוגדר מוחזקת אז - לא מצוירת, לא מדווחת, ולא נספרת במגבלת התדירות - ומועברת ל-callback ברגע שתגדירו אותו. קריאה ל-clearInAppMessageCallback() במקום זאת מסיימת את ההחזקה, וה-SDK מצייר את ההודעה המוחזקת.
זה לא משהו שמגדירים, אבל כדאי להכיר אותו כשמשהו לא מוצג. ה-SDK מאתר את הרנדרר המובנה שלו לפי שם בזמן ריצה, והמודול שבו השם הזה נמצא תלוי בדרך השילוב:
| אופן האריזה | המחלקה שה-SDK מחפש | |
|---|---|---|
| SwiftPM | JoryioUI הוא target נפרד | JoryioUI.DefaultInAppMessagePresenter |
| CocoaPods | Joryio/UI הוא subspec, ו-subspecs חולקים את המודול של ה-pod | Joryio.DefaultInAppMessagePresenter |
שני השמות נבדקים. אם שום הודעת In-App לא מופיעה, חפשו את האזהרה של ה-SDK
"No in-app presenter found" - משמעה שמוצר ה-UI לא מקושר (pod 'Joryio/UI',
או המוצר JoryioUI ב-SwiftPM), ולא שהקמפיין לא הגיע. מבחוץ שני המצבים נראים זהים.
ל-presenter משלכם אין צורך בכל זה: הציבו אותו, והגילוי האוטומטי לא ירוץ כלל.
סוגי הודעות
ה־SDK תומך ב־5 סוגי הודעות:
- Modal - במרכז המסך עם רקע מוצל
- Banner - בראש המסך
- Slide-Up - התראה קטנה מלמטה
- Full-Screen - הודעה שתופסת את כל המסך
- Custom - האפליקציה קובעת את המיקום
Banners ו־slide-ups מקבלים נגיעות רק בתוך ההודעה, כך ששאר האפליקציה נשארת שמישה מאחוריהם. הודעות Modal ו־Full-Screen חוסמות את המסך.
הגבלת תדירות
הודעות מכבדות את כללי המגע ברמת סביבת העבודה ואת מגבלות התדירות ברמת הקמפיין:
- מקסימום חשיפות בפרק זמן
- מינימום זמן בין הודעות
- מגבלות תדירות לכל קמפיין
מתי הודעה נחשבת כמוצגת
הודעה נחשבת כמוצגת - כלומר in_app.displayed מדווח ומגבלת התדירות של הקמפיין נצרכת - רק כשהאדם יכול היה לראות אותה. משך הזמן שעליה להיות על המסך הוא IN_APP_DISPLAY_DWELL_MS (1000 מילישניות), אותו קבוע בכל ה-SDK של Joryio.
- ה-SDK מציג הודעה רק כשהאפליקציה פעילה וה-view controller העליון יציב - לא באמצע הצגה, סגירה או הזזה. אחרת הוא ממתין ומנסה שוב.
- ההודעה נחשבת כמוצגת אחרי שהייתה על המסך שנייה אחת כשהאפליקציה נשארת פעילה. אם האפליקציה עוברת לרקע לפני כן, השנייה מתחילה מחדש כשהיא חוזרת.
- אם ההודעה יורדת מהמסך לפני כן, שום דבר לא מדווח והמגבלה לא נצרכת, כך שהיא נשארת זכאית.
- הקשה או סגירה לפני כן נחשבות כצפייה: קודם מדווח
displayed, ואחריו הלחיצה או הסגירה. איפוס, החלפת משתמש או ביטול הסכמה לפני כן לא מדווחים דבר. - אם אתם מציגים את ההודעות בעצמכם: דווחו
displayedלפי אותו כלל - אחרי שההודעה הייתה גלויהInAppDisplay.IN_APP_DISPLAY_DWELL_MS, או ממש לפני לחיצה או סגירה שמגיעות מוקדם יותר.
שליחת מאפיינים
setAttributes, setAttribute, incrementAttribute ו־unsetAttribute עמידים. כתיבה נשמרת בתור עד שהשרת מאשר אותה, כך שמאפיין שנקבע
כשהמכשיר לא מחובר נשלח כשהחיבור חוזר במקום להימחק. הגדלה נשלחת כהפרש אטומי, ולעולם לא כסכום שחושב במכשיר.
- ניסיון חוזר בכתיבת המאפיין הבאה, בחזרה לחזית, וכשהשליחה מתחדשת (
optIn(), מתן הסכמה). - מקובץ - רצף קריאות הופך לבקשה אחת (חלון של 800 מילישניות). כתיבה בודדת עדיין נשלחת מיד. כתיבה שנעשית בזמן שבקשה בדרך ממתינה לה ונשלחת אחריה; היא לעולם לא מבטלת אותה, כך שהגדלה לא נספרת פעמיים.
- רק השרת מאשר - תשובת
200עםsuccess: falseמשאירה את הכתיבות לניסיון הבא. כתיבה שהשרת דוחה כלא תקינה (HTTP 400, 413 או 422) מפוצלת והחצאים נשלחים שוב, כך שמפתח פגום אחד לא מעכב את האחרים; כתיבה בודדת שעדיין נדחית נזרקת ונרשמת ביומן. עד 200 מחיקות (unset) נשלחות בבקשה אחת, והשאר אחריהן. nilמוחק -setAttribute("nickname", value: nil), אוNSNullמגשר, מוחק את המאפיין כמוunsetAttribute.- נשמר ב־iOS Keychain, כך שכתיבה שורדת סגירה של התהליך. נמחק ברגע שהשרת מאשר, ומנוקה על ידי
optOut(),reset()ו-wipeData(). כתיבה שהשרת דוחה עם HTTP 403 נזרקת ונרשמת ביומן, כך שאינה יכולה לעכב כתיבות מאוחרות יותר.
מאפיינים משמשים גם למיקוד in-app. פרופיל השרת הוא המקור הקובע: רק כתיבות שהשרת עוד לא אישר יכולות לגבור עליו - וזה מה שמונע משני מכשירים של אותו איש קשר לחלוק על מי הוא.
אפשרויות תצורה
התאימו את התנהגות ה־SDK עם JoryioConfig:
JoryioConfig(
// User Identification
userId: String?, // Initialize with known user ID
anonymousId: String?, // Custom anonymous ID
// Batching & Performance
batchSize: Int, // Default: 50 (at most 500 per request)
flushInterval: TimeInterval, // Default: 5.0 seconds (minimum 1.0)
sendImmediately: Bool, // Default: false
maxQueueSize: Int, // Default: 1000
// Session Management
sessionTimeout: TimeInterval, // Default: 1800 (30 minutes)
trackSessionStart: Bool, // Default: true
// Push
requestPushPermissionAtLaunch: Bool, // Default: false - prefer a primer
// In-App Messaging
inApp: InAppConfig, // Default: InAppConfig() - see below
// Network & Retry
maxRetries: Int, // Default: 3
retryBackoffMs: Double, // Default: 1000.0
requestTimeout: TimeInterval, // Default: 10.0
// SDK Authentication (opt-in)
enableSdkAuthentication: Bool, // Default: false
sdkAuthenticationToken: String?, // Default: nil
// Privacy & GDPR
optOut: Bool, // Default: false (optOut() persists regardless)
trackingConsent: TrackingConsent?, // Default: nil (unset) - see Tracking consent
// Debugging
enableDebug: Bool, // Default: false
logLevel: LogLevel? // Default: nil - .debug with enableDebug, otherwise .error
)
InAppConfig(
enabled: Bool, // Default: true - false turns in-app off
allowHtmlJsInAppMessages: Bool, // Default: false
syncInterval: TimeInterval, // Default: 300 - seconds between automatic syncs
onMessageDisplay: ((InAppMessage) -> Void)?, // after the SDK displays a message
onMessageClick: ((InAppMessage, String) -> Void)?, // with the tapped element's name
onMessageDismiss: ((InAppMessage) -> Void)?, // after a message is dismissed
holdInAppMessagesForHandler: Bool // Default: false - hold messages until setInAppMessageCallback (since 1.3.2)
)
שלושת ה־callbacks רצים ב־main thread.
יכולות מתקדמות
ניהול מאפייני משתמש
// Set multiple attributes
Joryio.shared.setAttributes([
"age": 28,
"city": "San Francisco",
"premium": true
])
// Set single attribute
Joryio.shared.setAttribute("language", value: "en")
// Increment numeric attribute
Joryio.shared.incrementAttribute("page_views", by: 1)
Joryio.shared.incrementAttribute("total_spent", by: 29.99)
// Remove attribute
Joryio.shared.unsetAttribute("temporary_flag")
בקרות פרטיות
// הפסקת איסוף. נשמר במכשיר - שורד הפעלה מחדש של האפליקציה.
Joryio.shared.optOut()
// Opt back in
Joryio.shared.optIn()
// Check opt-out status
if Joryio.shared.isUserOptedOut() {
print("User has opted out")
}
// מחיקת כל מה שה-SDK שמר במכשיר הזה.
// נפרד מ-optOut(): "להפסיק לאסוף" ו"למחוק את מה שכבר נאסף" הן
// בקשות שונות. זו הפעולה שנדרשת לבקשת מחיקה. היא אינה מבטלת
// הסכמה - יש לקרוא גם ל-optOut() אם זו הכוונה.
Joryio.shared.wipeData()
// Get identity info
let (userId, anonymousId) = Joryio.shared.getIdentity()
print("User: \(userId ?? "anonymous"), Anonymous ID: \(anonymousId)")
מה בדיוק עושה optOut():
| מפסיק איסוף | כן - כל מסלול כתיבה: track, identify, מאפיינים (כולל הגדלות ומחיקות), כינויים (aliases), הרשמה למנוי, רישום פוש ואישורי מסירה, חשיפות In-App. חזרות מהסכמה עדיין נשלחות: ביטול מנוי, רשומת ה־opt-out וביטול רישום הפוש |
| שורד הפעלה מחדש | כן - הדגל נשמר במכשיר ונקרא לפני כל איסוף. הוא אינו קשור למפתח ה־SDK, ולכן שורד גם החלפת מפתח. optOut: true בתצורה נשמר באותה דרך |
| מוחק את מה שכבר בתור | כן - אירועים בתור, כתיבות מאפיינים שלא אושרו וכתיבות identify, alias ומנוי שממתינות נמחקים, לא נשלחים מאוחר יותר. חזרות מהסכמה שממתינות נשמרות ונמסרות |
| מעדכן את השרת | כן - מאפיין הפרופיל tracking_opted_out (true; optIn() שולח false). נשלח רק כשה־opt-out משתנה - כולל optOut: true בתצורה או קריאה לפני initialize() - ונשלח שוב עד שהשרת שומר אותו. הוא נשלח בכל מצב הסכמה - גם כשהיא ממתינה או נדחתה |
| מוחק מידע שמור | לא - יש להשתמש ב-wipeData() |
המאפיין tracking_opted_out הוא תיעוד, לא אכיפה: הוא נשמר בפרופיל כדי
שקמפיינים יוכלו להחריג לפיו. חסימה בצד השרת היא הגדרה נפרדת.
מה wipeData() מוחק: את מזהה המשתמש ואת המזהה האנונימי, את מזהה המכשיר, אירועים בתור, כתיבות מאפיינים שלא נשלחו, מצב In-App, הסשן ואסימון הפוש - שגם רישומו בשרת של Joryio מבוטל, כך ששום דבר נוסף לא נשלח להתקנה הזו. ההחרגה (opt-out) נשמרת: מחיקת הנתונים של אדם לא מחזירה אותו למעקב. האירוע הבא (או החזרה הבאה של האפליקציה לחזית) פותח סשן חדש, שמוכרז עם Session Start.
לאחר מכן הפוש נשאר כבוי עד שהאפליקציה קוראת שוב ל-registerPushToken או מבקשת הרשאה מחדש - טוקן שמגיע בהפעלה מאוחרת יותר לא נרשם אוטומטית. רישום וביטול רישום רצים אחד בכל פעם: רישום חוזר של אותו טוקן מבטל את ביטול הרישום שלו אם זה עדיין לא הגיע לשרת, וביטול רישום לא מאושר של טוקן אחר נשלח לפני כל רישום חדש.
הסכמת מעקב
העבירו בהפעלה את ההסכמה שמנהל ההסכמות שלכם מחזיק, ועדכנו אותה כשהמשתמש מחליט:
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_KEY",
apiHost: "https://api-eu1.joryio.com/api",
config: JoryioConfig(trackingConsent: .pending)
)
// Later, from your consent banner:
Joryio.shared.updateConsent(.granted) // or .denied
| הסכמה | התנהגות |
|---|---|
.granted | רגיל |
.pending | אירועים וכתיבות מאפיינים נשמרים במכשיר (עד maxQueueSize) ושום דבר לא נשלח - אין Session Start, אין סנכרון In-App, אין רישום פוש. .granted שולח את מה שנשמר; .denied מוחק אותו |
.denied | שום דבר לא נאסף, וכל מה שנשמר נמחק |
שני מתגים בלתי תלויים קובעים אם משהו נאסף: ההסכמה שהבאנר שלכם מדווח, וה**־opt-out** של האדם עצמו. איסוף מתבצע רק כשההסכמה היא .granted וגם האדם לא ביטל - כל "לא" גובר.
.deniedנשמר במכשיר ותקף גם בהפעלות הבאות. רקupdateConsent(.granted)מפורש, אוtrackingConsent: .grantedשמועבר בתצורה, מבטל אותו.- ברירת המחדל של
trackingConsentהיא לא מוגדר (nil). לא מוגדר וכלום לא שמור מתנהג כמו.granted, כך שאפליקציה בלי באנר הסכמה פשוט עובדת; לא מוגדר עם.deniedשמור נשאר דחוי. .pendingלא נשמר: הוא תקף להפעלה הנוכחית בלבד ולעולם לא גובר על.denied- שמור, או כזה שנקבע קודם לפניinitialize().- הסכמה לעולם לא מבטלת opt-out. רק
optIn()עושה זאת, ו־optOut()לעולם לא משנה את ההסכמה השמורה. getTrackingConsent()מחזיר את ההסכמה בתוקף;isUserOptedOut()משקף רק את ה־opt-out.- סשן שהתחיל כשהאיסוף לא היה מותר מוכרז באירוע Session Start ברגע שהאיסוף מותר - מאוחר יותר באותה הפעלה, או בהפעלה מאוחרת יותר כסשן חדש.
ריקון תור ידני
// Flush immediately (e.g., before app termination)
Joryio.shared.flush()
// Check queue size
let queueSize = Joryio.shared.getQueueSize()
print("Pending events: \(queueSize)")
אירועים נשלחים באצוות של עד 500 אירועים וכ־900 KB. אם השרת דוחה אצווה כלא תקינה (HTTP 400, 413 או 422), ה־SDK מפצל אותה ושולח מחדש את החצאים, כך שאירוע פגום אחד לא יכול לעכב את השאר; אירוע שנדחה לבדו נמחק ונרשם ביומן. אירועים שהשרת מדווח עליהם כנכשלים נשמרים ונשלחים שוב. HTTP 429, 5xx וכשלי רשת שומרים הכול ומנסים שוב עם השהיה מעריכית, תוך כיבוד Retry-After. HTTP 402 או 403 עוצרים את השליחה עד סוף הסשן ושומרים את האירועים. כשלים לעולם לא מוחקים אירועים; רק maxQueueSize מוחק, מהישן ביותר. ה־SDK זורק אירוע שהוא יודע שהשרת ידחה - שם ריק או ארוך מ־255 תווים, או מאפיינים מעל 50 KB בסריאליזציה (נמדדים כמו שהשרת מודד), מעל 200 מפתחות ברמה העליונה או קינון עמוק מ־5 רמות - כבר ב־track(), עם שורת יומן; גם אירועים שהשרת מציין ב־rejectedIndices נזרקים ולא נשלחים שוב. flush() מדלג על ההשהיה של ה־SDK עצמו אבל לעולם לא על Retry-After של השרת. עם אימות SDK אצווה לעולם לא מערבבת שני משתמשים, ואירועים בתור של משתמש שהתנתק נזרקים כשהשרת דוחה אותם מכל סיבת אימות SDK, כך שאינם יכולים לחסום את האירועים של המשתמש הבא.
כתיבות שחייבות להגיע לשרת
identify(_:), addAlias(_:name:), קריאות המנוי, רשומת tracking_opted_out והצד של השרת ב־unregisterPush() / wipeData() נשמרים במכשיר עד שהשרת מאשר אותם - גם אחרי הפעלה מחדש וגם אחרי reset() (כל אחד נושא את המזהים שתחתם נעשה). הם נשלחים לפי הסדר, אחד בכל פעם, ושינוי חדש יותר של אותו אדם לאותו ערוץ או לאותה קבוצה מחליף את זה שעדיין ממתין, כך שניסיון חוזר ישן לעולם לא מגיע אחריו. כתיבה שנעשתה כאנונימי ונשלחת אחרי identify(_:) יוצאת תחת מזהה המשתמש, כל עוד המזהה האנונימי שתחתיו נעשתה הוא עדיין של המכשיר הזה. כתיבה שהשרת דוחה סופית (HTTP 400, 403, 409, 413, 422) נזרקת ונרשמת ביומן; שינוי מנוי שנענה ב־404 (אין עדיין פרופיל) נשלח שוב אחרי שהאירועים שבתור נשלחו, עד 5 פעמים; success: false ו־401 של אימות SDK עבור המשתמש הנוכחי נשלחים שוב עם השהיה, עד 10 פעמים לכל כתיבה. היעדר רשת, פסקי זמן, HTTP 402, 429 ו־5xx לעולם לא נספרים נגד כתיבה. כל עוד קליטת הנתונים מושהית ב־workspace שלכם (HTTP 402), חזרות מהסכמה עדיין יוצאות לפני הכתיבות שצריכות להמתין. כש־1,000 כתיבות ממתינות, הישנה ביותר שאינה חזרה מהסכמה מפנה מקום. optOut(), הסכמה .denied ו־wipeData() זורקים את כתיבות ה־identify, ה־alias וההרשמה למנויים שעדיין ממתינות; חזרות מהסכמה - ביטול מנוי, רשומת tracking_opted_out, ביטול רישום פוש - נשמרות ונמסרות.
אימות SDK
הטוקן מוכיח מי המשתמש המזוהה: בקשות שמציינות מזהה משתמש חייבות לשאת טוקן תקף. למבקרים אנונימיים אין טוקן להציג, ולכן הבקשות שלהם מתקבלות בלעדיו, אלא אם סביבת העבודה הפעילה גם זהות מחמירה (strict identity).
כשאימות SDK מופעל בסביבת העבודה שלכם, כל בקשת קליטה חייבת לשאת JWT שהונפק על ידי השרת שלכם. אספקת אסימון - ב־sdkAuthenticationToken או באמצעות setSdkAuthenticationToken(_:), לפני או אחרי initialize() - מפעילה את הכותרת, עם או בלי enableSdkAuthentication:
Joryio.shared.setSdkAuthenticationToken(tokenFromYourBackend)
Joryio.shared.setSdkAuthenticationErrorHandler { error in
fetchFreshToken { Joryio.shared.setSdkAuthenticationToken($0) }
}
reset() מוחק את הטוקן. אירועים וכתיבות שהמשתמש היוצא כבר הכניס לתור עדיין נשלחים עם הטוקן שלו, כך שהתנתקות רגילה לא מאבדת דבר - הטוקן נשמר ב־Keychain, קשור לפריטים האלה, עד שהם נשלחים או נזרקים, כך שגם סגירת האפליקציה מיד אחרי התנתקות לא מאבדת דבר. זיהוי משתמש אחר בלי reset() עושה את אותו הדבר עם הטוקן של המשתמש הקודם, וכך גם הגדרת הטוקן של המשתמש הבא עם setSdkAuthenticationToken(_:): בין אם מגדירים אותו לפני identify ובין אם אחריו, השאריות של המשתמש הקודם שומרות על הטוקן שלו. כל בקשה נושאת את הטוקן שה־sub שלו הוא המשתמש של אותה בקשה, ולעולם לא טוקן שמציין מישהו אחר; טוקן שהוגדר למשתמש הבא לפני reset() - גם כשאף אחד אינו מחובר - נשמר עבורו ומשמש שוב ב־identify שלו, ו־wipeData() מוחק אותו - אלא אם זה הטוקן של המשתמש שנמחק (גם משתמש שזוהה כשהמעקב כבוי), או הטוקן שסופק להפעלה הזו כש־wipeData() שנקרא לפני initialize() רץ. אירועים וכתיבות שהשאיר משתמש שכבר אינו הנוכחי נזרקים כשאימות ה־SDK דוחה אותם, מכל סיבה (missing, sub_mismatch, expired, invalid), כך שאינם חוסמים את אלה של המשתמש הבא. מטפל השגיאות שומע רק על בקשות של המשתמש הנוכחי - לעולם לא על שאריות של משתמש קודם. גם כשהאדם בחר שלא להיות במעקב או שההסכמה נדחתה, הגדירו את הטוקן של המשתמש בעת ההתחברות: המשתמש שזוהה בינתיים נחשב לנוכחי עבור ביטול המנוי שלו, כך שאם הוא נדחה, המטפל מתבקש לספק את הטוקן שלו וביטול המנוי נשמר.
מדריך API
כל חבר ציבורי של Joryio.shared, בשמו המדויק ב־Swift.
| מתודה | מה היא עושה |
|---|---|
initialize(sdkKey:apiHost:config:) | מפעילה את ה־SDK עם מפתח ה־SDK, מארח ה־API ו־JoryioConfig אופציונלי. קריאה שנייה מתעלמת |
track(_:properties:) | רושמת אירוע עם מאפיינים אופציונליים |
trackScreen(_:properties:) | רושמת אירוע Screen Viewed עם screen_name |
identify(_:) | מצרפת את המכשיר למזהה משתמש; רושמת פוש מחדש ומסנכרנת הודעות In-App |
addAlias(_:name:) | מצמידה מזהה שני עם תווית, גם למבקרים אנונימיים |
reset() | התנתקות: זהות אנונימית וסשן חדשים |
setAttributes(_:) | קובעת כמה מאפיינים; ערכי nil / NSNull מוחקים |
setAttribute(_:value:) | קובעת מאפיין אחד; nil מוחק אותו |
incrementAttribute(_:by:) | הגדלה מספרית אטומית (ברירת מחדל 1) |
unsetAttribute(_:) | מוחקת מאפיין |
getUserId() | מזהה המשתמש הנוכחי, או nil |
getAnonymousId() | המזהה האנונימי הנוכחי |
getIdentity() | (userId, anonymousId) |
getSessionId() | מזהה הסשן הנוכחי |
getSessionInfo() | (sessionId, sessionStart, duration) |
optOut() | עוצרת כל איסוף ושליחה; נשמר |
optIn() | חידוש אחרי optOut() |
isUserOptedOut() | האם המשתמש בחר שלא להיות במעקב; לפני initialize(), לפי מה שנשמר |
getTrackingConsent() | ההסכמה למעקב שבתוקף: .granted, .denied או .pending |
updateConsent(_:) | קובעת את הסכמת המעקב: .granted, .pending או .denied |
wipeData() | מוחקת את כל מה שה־SDK שמר במכשיר; שומרת את ה־opt-out |
setSubscription(channel:status:) | סטטוס מנוי שיווקי לערוץ אחד |
addToSubscriptionGroup(_:channel:) | הצטרפות לקבוצת מנוי בערוץ |
removeFromSubscriptionGroup(_:channel:) | עזיבת קבוצת מנוי בערוץ |
setSdkAuthenticationToken(_:) | מספקת או מחליפה את ה־JWT של אימות ה־SDK |
setSdkAuthenticationErrorHandler(_:) | נקראת כשהשרת דוחה את ה־JWT; השיבו באסימון חדש |
clearSdkAuthenticationErrorHandler() | מסירה את המטפל הזה, למשל כשגשר או מסך נסגרים |
flush() | שולחת עכשיו אירועים מהתור |
getQueueSize() | מספר האירועים שממתינים במכשיר |
lastTransportError | כישלון הבקשה האחרון (JoryioTransportError), או nil אחרי הצלחה |
currentApiEndpoint | כתובת הבסיס של ה־API שאליה ה־SDK שולח |
isInitializationPending | true כל עוד initialize() ממתין לפתיחת הנעילה הראשונה מאז שהמכשיר הודלק; אינו כשל |
ecommerce(config:) | כלי המסחר האלקטרוני; העבירו תצורה כדי לקבוע מטבע |
inAppPresenter | InAppMessagePresenter משלכם, במקום הרנדרר המובנה |
setInAppMessageCallback(_:capabilities:) | קבלת הודעות In-App וציור שלהן בעצמכם |
clearInAppMessageCallback() | החזרת הציור ל־SDK |
trackInAppImpression(campaignId:action:elementId:) | דיווח על מה שהממשק שלכם עשה עם הודעה |
submitInAppForm(campaignId:values:) | שליחת טופס שמוצג בהודעה (async); לעולם לא זורקת שגיאה |
syncInAppCampaigns() | משיכת קמפייני In-App עכשיו (async) |
evaluateInAppCampaigns() | לבדיקות בלבד, דורש enableDebug: הערכת קמפיינים עכשיו |
resetDisplayedCampaigns() | לבדיקות בלבד, דורש enableDebug: שכחת ההודעות שהוצגו |
requestPushPermission() | בקשת הרשאת התראות (async); מבטלת את unregisterPush() |
getPushPermissionStatus() | "granted", "denied" או "notDetermined" (async) |
isPushEnabled() | true בדיוק כשהסטטוס הוא "granted" (async) |
didRegisterForRemoteNotifications(deviceToken:) | העברת אסימון ה־APNs מה־AppDelegate |
didFailToRegisterForRemoteNotifications(error:) | העברת כישלון רישום APNs |
didReceiveRemoteNotification(_:completionHandler:) | העברת התראה מרוחקת |
registerPushToken(_:onResult:) | רישום אסימון hex בעצמכם, עם callback לתוצאה; מבטלת את unregisterPush() |
getDeviceToken() | אסימון ה־APNs השמור, או nil |
unregisterPush() | הסרת האסימון בשרת וב־iOS; נשמר בין הפעלות |
reportPushDelivered(_:) | דיווח שפוש הגיע, לפי ה־trackingId שלו |
trackPushClick(trackingId:) | דיווח על הקשה על פוש, לפי ה־trackingId שלו |
updateBadgeCount(_:) | קביעת תג האפליקציה |
clearBadge() | ניקוי תג האפליקציה |
Joryio.pushPermissionAttribute | שם המאפיין push_permission |
שיטות עבודה מומלצות
1. מעקב אחר אירועים משמעותיים
התמקדו באירועים חשובים לעסק:
// Good: Specific, actionable events
Joryio.shared.track("Trial Started", properties: ["plan": "premium"])
Joryio.shared.track("Feature Used", properties: ["feature": "export"])
// Avoid: Overly generic events
Joryio.shared.track("Button Tapped") // Too generic
2. ניהול מחזור חיי משתמש
// On login
func handleLogin(userId: String, userInfo: UserInfo) {
Joryio.shared.identify(userId)
Joryio.shared.setAttributes([
"email": userInfo.email,
"name": userInfo.name
])
}
// On logout
func handleLogout() {
Joryio.shared.reset()
}
// On signup - identify() joins the anonymous history to the new user
func handleSignup(userId: String, userInfo: UserInfo) {
Joryio.shared.identify(userId)
Joryio.shared.setAttributes(userInfo.attributes)
}
3. ריקון באירועים קריטיים
override func applicationWillTerminate(_ application: UIApplication) {
Joryio.shared.flush()
}
פתרון תקלות
אירועים לא מופיעים
- ודאו שמפתח ה־SDK נכון:
jry_sdk_ios_* - הפעילו יומני ניפוי שגיאות:
enableDebug: true(רושם הכול, אלא אם הגדרתם גםlogLevel) - בדקו שגיאות בקונסול
- ודאו קישוריות רשת
- קראו
flush()לשליחה מיידית
פוש לא עובד
- ודאו שאישורי ה־APNs שמורים באפליקציה בלוח הבקרה
- בדקו שמזהה המכשיר נרשם
- ודאו entitlements מתאימים ב־Xcode
- בדקו בסביבה הנכונה (dev מול production)
- חפשו את השגיאה מ־שלח push לבדיקה בקבלת אישורי APNs
שגיאות בנייה
- ודאו יעד פריסה iOS 14.0+
- נקו תיקיית build: Cmd+Shift+K
- עדכנו תלויות Swift Package
מעקב מסחר אלקטרוני
iOS SDK כולל מעקב מובנה אחר מסחר אלקטרוני לאירועי מוצרים, עגלה, תשלום והזמנות. ראו את מדריך מעקב המסחר האלקטרוני המשותף לכל ה-SDKs לתיעוד המלא עם דוגמאות Swift.
let ecommerce = Joryio.shared.ecommerce(config: EcommerceConfig(currency: "USD"))
ecommerce.purchase(order: EcommerceOrder(
orderId: "ORD-1001",
items: [EcommerceCartItem(productId: "SKU123", name: "Blue Shirt", price: 29.99, quantity: 2)],
value: 59.98
))
קריאה שחסר בה מה שנותן לאירוע משמעות - productId, orderId, מחיר או ערך סופי, רשימת items ברכישה (יכולה להיות ריקה) - כותבת אזהרה ביומן ולא שולחת דבר, לעולם לא הזמנה ריקה או באפס.
purchase שולח Order Completed עם המפתחות שדוחות ההכנסות קוראים: orderId ו־total (וגם currency והפריטים). אם אתם עוקבים אחרי רכישה בעצמכם עם track(), השתמשו באותם מפתחות - order_id ו־value אינם נקראים, וההזמנה נספרת כהכנסה של אפס.
צעדים הבאים
- Android SDK - שילוב Android SDK
- מדריך Push Notifications - למידה על קמפיינים של פוש
- מדריך In-App Campaigns - יצירת הודעות בתוך האפליקציה
- אירועים מותאמים - מעקב אחרי אירועים מותאמים
- E-Commerce API
רישום לפוש והסכמה
ה-SDK נרשם להתראות מרוחקות בין אם המשתמש אישר התראות ובין אם לא. ב-iOS אלו שני דברים נפרדים: הרישום מפיק אסימון מכשיר ללא הסכמה, והאסימון הזה יכול להעביר רק פוש ברקע (שקט) - הוא לא יכול להציג דבר שהמשתמש לא אישר.
זה מה שמאפשר להודעת in-app להגיע גם למשתמש שסירב להתראות, ומשמעו שאסימון כבר קיים אם ידליק התראות בהגדרות בהמשך. יש לציין זאת במדיניות הפרטיות.
מנוי שיווקי
האם מותר לשלוח הודעות לאדם הזה בערוץ מסוים. זו הסכמה שונה מ-optOut(), שעניינה האם מותר לעקוב אחריו - אחת אינה גוררת את השנייה, ואפשר לסרב למעקב ועדיין לרצות את הניוזלטר.
זה מה שמרכז העדפות בתוך האפליקציה כותב.
Joryio.shared.setSubscription(channel: "email", status: "unsubscribed")
Joryio.shared.addToSubscriptionGroup("list_abc123", channel: "email")
שינוי מנוי שנעשה בזמן שהסכמת המעקב ממתינה (pending) נשמר במכשיר ונשלח כשההסכמה ניתנת. שינוי שנכשל נשמר, גם אחרי הפעלה מחדש ו־reset(), ונשלח שוב - ראו כתיבות שחייבות להגיע לשרת.
ביטול מנוי - status: "unsubscribed", או removeFromSubscriptionGroup - הוא חזרה מהסכמה: הוא מתקבל ונמסר גם כשהאדם בחר שלא להיות במעקב, כשההסכמה ממתינה או נדחתה, או אחרי wipeData(). הרשמה למנוי או הצטרפות לקבוצה - לא.
ערוצים: email, sms, whatsapp, push, viber. סטטוסים: optedIn, subscribed, unsubscribed.
נדרש מזהה - הקריאה מדולגת, עם שגיאה ביומן, אם אין מזהה משתמש ואין מזהה אנונימי.