Dijji Dijji Intelligence·Engagement·Defense
Install → iOS

Dijji for iOS.

Swift SDK for analytics, push notifications via APNs, in-app messages (banner / bottom-sheet / modal), and crash capture — including pure-Swift crashes via signal handler. Matches the Android SDK's two-line integration.

Live v1.2.0-alpha SwiftPM · iOS 13+ Apache 2.0 · open source
DijjiCore (required)
  • Two-line Dijji.initialize(siteKey:)
  • Lifecycle auto-capture: app_open / background / session / install
  • Custom events · Dijji.track
  • User properties · Dijji.setUserProperty
  • identify · optIn / optOut
  • NSException crash chain (Obj-C + bridged Swift)
  • POSIX signal crash handler — pure-Swift crashes (fatalError, forced unwrap, OOB) via SIGABRT/SIGTRAP/SIGSEGV/SIGBUS/SIGILL/SIGFPE
  • Rich device context (screen, locale, network, battery, memory, disk, hardware id)
DijjiPush (optional)
  • Token registration · DijjiPush.registerToken
  • Push tap handling · handleNotification
  • Auto deep-link routing
  • push_received + push_opened events fire automatically
  • Rich push helper (DijjiNotificationServiceHelper) — image attachment in 5 lines from your NSE target
DijjiMessages (optional)
  • UIKit renderer for banner / bottom-sheet / modal
  • 9 themes (purple/cyan/emerald/amber/rose/indigo/slate/mono + hex)
  • Drag-to-dismiss on sheets · auto-dismiss on banners
  • Queue + present (one message visible at a time)
  • Auto-fires __dijji_message_received / _clicked / _dismissed

01Quickstart — two lines.

In Xcode, File → Add Package Dependencies, paste:

https://github.com/urbaneyed/dijji-ios

Pick the modules you want. DijjiCore is required; DijjiPush and DijjiMessages are optional.

Then in your AppDelegate (or App body for SwiftUI):

import DijjiCore

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ app: UIApplication,
                     didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
        Dijji.initialize(siteKey: "ws_a647ba153d0911f1b7")
        return true
    }
}

That's it. The SDK auto-captures app_open, app_background, session_start/end, and app_install (once on first launch). Custom events via Dijji.track; user properties via Dijji.setUserProperty; opt-out via Dijji.optOut().

02Configure APNs in Site Settings.

Before any iOS push reaches a device, your Dijji site needs four pieces of Apple credentials. Generate them in the Apple Developer portal, then upload at /app/sites/{site_key}/mobile.

Production vs Sandbox. TestFlight + App Store builds use the production endpoint. Xcode debug builds use sandbox. The same key works for both; only the endpoint URL differs — pick the right one in the upload form. We default to production.

The .p8 contents are encrypted at rest (AES-256-CBC, AES key derived from dijji.cryptoKey in the .env). Decrypted only at push-dispatch time, never exposed via API.

03REST API — five endpoints.

An iOS dev who can't wait for the Swift SDK can integrate against these directly. Same endpoints the Android SDK uses; the only difference is what your client sends as platform.

MethodPathPurpose
POST /t/app/collect Batch event ingestion. JSON body: {site, visitor_id, events: [{name, ts, props}]}. Auto-events the SDK fires: app_open, app_background, screen_view, session_start, session_end, app_install. Custom events go through the same channel.
POST /t/app/install Fired once on first ever launch. Body: {site, visitor_id, device_id, platform: "ios", os, model, locale, ...}. Backend persists to dijji_app_installs + creates the user row in dijji_app_users.
POST /t/app/token Register an APNs device token. Body: {site, visitor_id, token, platform: "ios"}. Stored in dijji_push_tokens; revoked automatically when APNs returns BadDeviceToken or Unregistered.
POST /t/app/crash Crash report ingestion. Body includes the stack, signal/exception type, breadcrumbs, app + device state. Synchronous on the client (3s timeout) so the report doesn't get lost when the process dies.
POST /t/app/session Session rollup — upsert keyed on session_id. Sent on UIApplicationDidEnterBackgroundNotification with duration, screen count, event count.
GET /t/app/inbox Fetch pending in-app messages. Query: ?site=...&visitor=...&platform=ios. Returns banner / bottom_sheet / modal payloads with personalization tokens already merged. Marks messages delivered on read.
GET /t/rules Mobile config: kill switch, rollout %, poll/flush intervals, sample rates. Query: ?platform=ios. Cache for 5 min on the client.

Push payload received from APNs

When a Dijji-dispatched push arrives, the JSON looks like:

{
    "aps": {
        "alert": { "title": "Welcome back", "body": "We picked 3 jobs for you" },
        "sound": "default"
    },
    "dijji": "1",         // flag to claim Dijji-originated pushes
    "deep_link": "/jobs",
    "trigger_id": "42",
    "push_id": "199"
}

In your UNUserNotificationCenterDelegate, claim notifications with userInfo["dijji"] == "1", fire a push_opened event with push_id + trigger_id, and route on deep_link.

04Rich pushes — add a Notification Service Extension.

For pushes with images on iOS, your app needs a Notification Service Extension. Apple delivers the push to the extension first; the extension downloads the image, attaches it, then hands the result to iOS for display. Without an extension, iOS shows the text-only payload.

Adding one is a 5-line job:

  1. In Xcode: File → New → Target → Notification Service Extension. Name it whatever you like (e.g. DijjiNotificationService).
  2. Add DijjiPush as a dependency of the new target.
  3. Replace the generated NotificationService.swift body with:
import UserNotifications
import DijjiPush

class NotificationService: UNNotificationServiceExtension {
    override func didReceive(_ request: UNNotificationRequest,
                              withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        DijjiNotificationServiceHelper.handle(request, contentHandler: contentHandler)
    }
}

That's it. The helper looks for image_url in the push payload (which the Dijji backend stamps when you fill the "Image URL" field in the push composer or trigger), downloads it with a 25-second cap, infers the file extension from Content-Type, attaches it as UNNotificationAttachment, and hands the rendered content back to iOS. Falls back to text-only on any download failure rather than dropping the push.

Android needs nothing extra. Image URL renders natively as a BigPictureStyle notification on Android. iOS is the only platform that requires the extension.

05Privacy by default.

06Live Activities — lock screen + Dynamic Island.

iOS 16.1+ adds a new push-driven UI surface: Live Activities show on the lock screen and the Dynamic Island, updated in real time by the server. Dijji's DijjiLiveActivity module handles the APNs side — you keep ownership of the SwiftUI layout, we deliver the state pushes.

1. Add the module to your Package.swift

.product(name: "DijjiLiveActivity", package: "dijji-ios")

2. Declare your ActivityAttributes in your app target

import ActivityKit

struct OrderTrackingAttributes: ActivityAttributes {
  public struct ContentState: Codable, Hashable {
    var status: String         // "Confirmed" | "Out for delivery" | "Delivered"
    var etaMinutes: Int
  }
  var orderId: Int
  var restaurantName: String
}

3. Start the activity + register with Dijji

import ActivityKit
import DijjiLiveActivity

let attrs = OrderTrackingAttributes(orderId: 42, restaurantName: "Baang")
let initial = OrderTrackingAttributes.ContentState(
  status: "Confirmed", etaMinutes: 35
)

if let activity = try? Activity<OrderTrackingAttributes>.request(
  attributes: attrs,
  contentState: initial,
  pushType: .token
) {
  // ONE LINE — auto-registers every push token Apple rotates to.
  DijjiLiveActivity.observe(activity, activityId: "order_42")
}

// When the activity ends:
DijjiLiveActivity.end(activityId: "order_42")

4. Build the widget UI in your Widget Extension

Standard SwiftUI ActivityConfiguration — lock screen + Dynamic Island compact / minimal / expanded. Apple's HIG covers the layout patterns.

5. Push state updates from the dashboard

From /live, pick the user's mobile card → pick "Live Activity update" → type the new content_state JSON. The server signs an APNs liveactivity push and Apple updates the layout on-device within seconds.

From a trigger or journey, queue an action_type=live_activity_update push with action_config = {activity_id, content_state, alert?} — same dispatch path, same dashboard.

07Roadmap.

What's shipped, what's next:

VersionScopeStatus
BackendAPNs dispatcher, schema, push routing, uninstall sweep, settings UI, push performance dashboardLive
SDK v1.0-alphaTwo-line init, lifecycle auto-capture, custom events, user properties, NSException crash chain, push token registration, deep linksLive
SDK v1.1-alphaUIKit in-app message renderer (banner / sheet / modal) with theme palette, drag-to-dismiss, queue-and-presentLive
SDK v1.2-alphaPOSIX signal crash handler — catches pure-Swift crashes (fatalError, forced unwrap nil, OOB) via SIGABRT/SIGTRAP/SIGSEGV/SIGBUS/SIGILL/SIGFPELive
SDK v1.3-alpha4 new in-app formats: in_app_hero (full-bleed, dual CTA), in_app_nps (0–10 score), in_app_reactions (emoji), in_app_countdown (live ticker). image_url support across banner/sheet/modal.Live
SDK v1.4-alphaLive Activities (iOS 16.1+) — new DijjiLiveActivity module bridges your Activity<Attrs>.pushTokenUpdates to Dijji's APNs dispatcher. Lock screen + Dynamic Island layouts driven by server-pushed content state. Order tracking, booking countdowns, score tickers — all without your own push infra.Live
v1.5Mach exception port handler · server-side dSYM symbolicationNext
v1.5+Carousel push · quick-reply · action buttons · time-sensitive notifications · SwiftUI-native message componentsPlanned