Allmine API

Live Activity — mobil entegrasyon

~10 dkMobil / WebKararlı

iOS Live Activity ve push tetikleyicileri

Scheduled Live Activity Mobile Entegrasyonu

Migration rehberi

Yapılandırma sırası için Migration şablonu. Yeni düzenlemelerde Amaç → Önkoşullar → Endpoint → Request/Response → Hata kodları → Client adımları → İlgili sayfalar bölümlerini tercih edin.

Bu doküman, planli yayinlar için creator ve guest kullanıcılarına yayin baslamadan 60 dakika once backend tarafından tetiklenen Live Activity countdown akisinin mobil entegrasyonunu açıklar.

Kapsam:

  • Yalnizca iOS Live Activity entegrasyonu
  • Backend-driven remote start
  • Widget tarafında local countdown render edilmesi

Bu akista backend start event'i yollar ve runtime token kaydi yapildiktan sonra gerekli durumlarda end event'i de gönderebilir. Arada periyodik update gönderilmez. Countdown tamamen mobilde scheduledStartAt uzerinden hesaplanır.

Ozet

Akis su sekilde calisir:

  1. Mobil uygulama FCM token, deviceId ve ActivityKit push-to-start token bilgisini backend'e kaydeder.
  2. Backend, yayin planliysa plannedStartDate - 60 dakika anina job schedule eder.
  3. Zaman geldiginde backend creator ve guest kullanıcılarinin uygun iOS cihazlarina remote-start Live Activity push'u yollar.
  4. iOS sistemi Live Activity'yi baslatir.
  5. Mobil uygulama runtime liveActivityToken bilgisini backend'e kaydeder.
  6. Mobil uygulama countdown UI'ini lokal olarak scheduledStartAt uzerinden render eder.
  7. Stream cancelled/ready/started/expired oldugunda backend runtime token uzerinden end push gönderebilir.

Minimum Gereksinimler

Bu backend akisi remote-start kullandigi için mobil tarafta efektif minimum sürüm iOS 17.2 olarak düşünülmelidir. Firebase'in resmi dokümanı da remote-start için iOS 17.2 gerektigini belirtir. Live Activity UI kabiliyeti daha eski sürümlerde bulunsa da bu backend akisi o cihazlarda otomatik baslatma yapmaz.

Zorunlu gereksinimler:

  • iOS target'inda ActivityKit
  • Push Notifications capability
  • Live Activities capability
  • Info.plist içinde NSSupportsLiveActivities = YES
  • FCM Apple platform kurulumu
  • Backend'in gönderdigi bundle/Firebase konfigurasyonu ile ayni iOS uygulama hedefi

Not:

  • Uygulama React Native veya Flutter olsa bile Live Activity kismi native iOS katmaninda implement edilmelidir.
  • Shared katman sadece backend API cagrilarini ve deep link routing'i yonetebilir.

Backend Tarafi Kontrat

1. Device kaydi

Endpoint: POST /notifications/device-token
Auth: Opsiyonel, ancak creator/guest eslesmesi için kullanıcı login durumunda gönderilmelidir.

Request body:

{
  "token": "<fcm_token>",
  "platform": "ios",
  "deviceId": "<stable_device_id>",
  "timezone": "Europe/Istanbul",
  "liveActivityPushToStartToken": "<activitykit_push_to_start_token>"
}

Alanlar:

  • token: FCM registration token
  • platform: bu akis için ios
  • deviceId: cihaz bazli sabit kimlik, ayni cihaz için degismemeli
  • timezone: opsiyonel
  • liveActivityPushToStartToken: ActivityKit push-to-start token

Kurallar:

  • liveActivityPushToStartToken gönderiliyorsa deviceId zorunludur.
  • deviceId her zaman ayni fiziksel cihaz için stabil kalmalidir.
  • deviceId cihaz modeli olmamalidir. Ornek olarak iphone-15-pro-max yerine install/device bazli stabil bir UUID benzeri deger kullanılmalıdır.
  • Kullanıcı login olduktan sonra bu endpoint tekrar cagrilmalidir; aksi halde cihaz kaydi kullanıcıyla iliskilenmeyebilir.

Sadece push-to-start token guncellemek için mevcut cihaz kaydi varsa su body de gönderilebilir:

{
  "platform": "ios",
  "deviceId": "<stable_device_id>",
  "liveActivityPushToStartToken": "<new_push_to_start_token>"
}

Response:

{
  "success": true
}

2. Runtime session kaydi

Endpoint: POST /notifications/live-activity-sessions
Auth: Zorunlu

Request body:

{
  "streamId": "65f000000000000000000001",
  "deviceId": "<stable_device_id>",
  "liveActivityToken": "<runtime_live_activity_token>",
  "activityId": "optional-activity-id"
}

Alanlar:

  • streamId: ilgili scheduled stream kimligi
  • deviceId: daha once device-token kaydinda kullanilan ayni cihaz kimligi
  • liveActivityToken: remote-start sonrasi ActivityKit tarafından verilen runtime token
  • activityId: opsiyonel local activity identifier

Response:

{
  "success": true
}

3. Runtime session cleanup

Endpoint: DELETE /notifications/live-activity-sessions/:streamId/:deviceId
Auth: Zorunlu

Bu endpoint, mobil uygulama local olarak dismiss edilen veya manuel kapatilan activity için backend kaydini temizlemek amaciyla kullanılır.

Ne zaman cagrilmali:

  • Kullanıcı Live Activity'yi local olarak dismiss ederse
  • Mobil taraf activity'yi kendi karariyla manuel olarak sonlandirirsa
  • Uygulama acilisinda local tarafta activity artık yok ama backend session kaydi oldugunu dusundugunuz bir reconciliation akisi varsa

Ne zaman cagrilmamali:

  • Backend end push gönderip activity'yi kapatiyorsa ekstra cleanup çağrısı zorunlu değildir
  • Her app launch'ta kosulsuz cagrilmamalidir

Ornek request:

DELETE /notifications/live-activity-sessions/65f000000000000000000001/<stable_device_id>
Authorization: Bearer <access_token>

Beklenen response:

  • 204 No Content

Notlar:

  • deviceId, POST /notifications/device-token ve POST /notifications/live-activity-sessions cagrilarinda kullandiginiz ayni stabil cihaz kimligi olmalidir
  • Bu endpoint sadece backend'deki runtime session kaydini temizler; local ActivityKit kapatma islemi mobil tarafta ayrica yapilmalidir

ActivityKit Model Sozlesmesi

Backend, start payload'inda attributes-type olarak sabit su string'i kullanir:

AllmineLiveActivityAttributes

iOS tarafındaki ActivityAttributes tipi bu adla eslesmelidir. Tip adi değişirse backend de güncellenmelidir.

Attributes payload

Start aninda gelen attributes:

{
  "streamId": "65f000000000000000000001",
  "creatorName": "Selin Kaya • Mert Demir",
  "streamTitle": "Yayin basligi",
  "title": "Yayin basligi",
  "role": "creator",
  "deepLink": "allmine://live-stream/65f000000000000000000001"
}

Content state payload

Start aninda gelen content-state:

{
  "streamId": "65f000000000000000000001",
  "creatorName": "Selin Kaya • Mert Demir",
  "streamTitle": "Yayin basligi",
  "statusText": "Yayina kalan sure",
  "subtitle": "Yayina katilmak için dokun",
  "viewerCount": 128,
  "deepLinkUrl": "allmine://home",
  "updatedAt": 1776384000,
  "title": "Yayin basligi",
  "role": "creator",
  "deepLink": "allmine://live-stream/65f000000000000000000001",
  "scheduledStartAt": 1776708000,
  "displayState": "countdown"
}

End aninda backend tarafından gelebilecek content-state ornegi:

{
  "streamId": "65f000000000000000000001",
  "statusText": "Yayin sona erdi",
  "subtitle": "Ana sayfaya donmek için dokun",
  "viewerCount": 128,
  "deepLink": "allmine://home",
  "deepLinkUrl": "allmine://home",
  "updatedAt": 1776387600,
  "displayState": "ended",
  "endedReason": "cancelled"
}

Swift model onerisi

scheduledStartAt backend tarafında epoch seconds integer olarak gönderilir. Bu nedenle ContentState içinde bunu Int? olarak tutup widget/render katmaninda Date(timeIntervalSince1970:) ile çevirmek uygun yaklasimdir.

import ActivityKit

struct AllmineLiveActivityAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var streamId: String
        var creatorName: String?
        var streamTitle: String?
        var statusText: String?
        var subtitle: String?
        var viewerCount: Int?
        var deepLinkUrl: String?
        var updatedAt: Int?
        var title: String?
        var role: String?
        var deepLink: String?
        var scheduledStartAt: Int?
        var displayState: DisplayState
        var endedReason: EndedReason?
    }

    var streamId: String
    var creatorName: String
    var streamTitle: String
    var title: String?
    var role: String?
    var deepLink: String
}

enum DisplayState: String, Codable, Hashable {
    case countdown
    case ended
}

enum EndedReason: String, Codable, Hashable {
    case ready
    case started
    case cancelled
    case expired
}

Notlar:

  • streamId hem attributes hem content-state içinde gelir; activity ve stream eslesmesi için attributes.streamId kullanmak en temiz yaklasimdir.
  • Backend countdown ve ended state'lerini gönderebilir.
  • creatorName, backend tarafında creator ve guest display name'lerinin tek string halınde birleştirilmiş halidir.
  • statusText, countdown numeriginden ayri bir sabit label olarak gösterilmelidir.
  • streamTitle, subtitle, viewerCount, deepLinkUrl ve updatedAt alanlari mobil widget'in mevcut decode ihtiyaci için gönderilir.

iOS Tarafinda Uygulanacak Akis

1. Uygulama acilisinda observer'lari baslat

Remote-start akisinin calismasi için pushToStartTokenUpdates akisina abone olunmalidir.

Onerilen bootstrap:

import ActivityKit

final class LiveActivityBootstrapper {
    func start() {
        observePushToStartToken()
    }

    private func observePushToStartToken() {
        guard #available(iOS 17.2, *) else { return }

        Task {
            if let token = Activity<AllmineLiveActivityAttributes>.pushToStartToken {
                await registerPushToStartToken(token)
            }

            for await token in Activity<AllmineLiveActivityAttributes>.pushToStartTokenUpdates {
                await registerPushToStartToken(token)
            }
        }
    }
}

Yukaridaki akis framework-agnostic düşünülmelidir. React Native veya Flutter kullaniyorsaniz bu kisim native iOS target'inda calismalidir.

2. Push-to-start token'i backend'e kaydet

Activity<AllmineLiveActivityAttributes>.pushToStartToken veya pushToStartTokenUpdates ile gelen Data hex string'e cevrilip POST /notifications/device-token endpoint'ine gönderilmelidir.

Onerilen helper:

func hexString(from data: Data) -> String {
    data.map { String(format: "%02x", $0) }.joined()
}

Kayit sirasinda su veriler her zaman birlikte gönderilmelidir:

  • token: FCM token
  • platform: ios
  • deviceId: sabit cihaz kimligi
  • liveActivityPushToStartToken: hex string token

Pratik oneriler:

  • FCM token degistiginde tekrar gönderin.
  • push-to-start token degistiginde tekrar gönderin.
  • Login sonrasi tekrar gönderin.
  • App cold start'ta tekrar göndermek zararli değildir; backend idempotent sekilde gunceller.

3. Runtime token'i backend'e kaydet

Remote-start ile baslayan activity'nin runtime token'i backend'e gönderilmelidir. Bu adim olmadan backend end push'unu gönderemez.

Onerilen akis:

guard #available(iOS 17.2, *) else { return }

Task {
    for await activity in Activity<AllmineLiveActivityAttributes>.activityUpdates {
        for await token in activity.pushTokenUpdates {
            await registerLiveActivitySession(
                streamId: activity.attributes.streamId,
                deviceId: currentDeviceId,
                liveActivityToken: hexString(from: token),
                activityId: activity.id
            )
        }
    }
}

Bu kayit POST /notifications/live-activity-sessions endpoint'ine gönderilmelidir.

3.1 Local dismiss veya manual end oldugunda cleanup gönder

Runtime session kaydi yapildiysa, activity local tarafta kullanıcı veya uygulama tarafından kapatildiginda backend session kaydi da temizlenmelidir.

Onerilen akis:

guard #available(iOS 17.2, *) else { return }

Task {
    for await activity in Activity<AllmineLiveActivityAttributes>.activityUpdates {
        for await state in activity.activityStateUpdates {
            if state == .dismissed {
                await deleteLiveActivitySession(
                    streamId: activity.attributes.streamId,
                    deviceId: currentDeviceId
                )
            }
        }
    }
}

Uygulama kendi tarafında explicit activity.end(...) çağırıyorsa, ayni cleanup request'i bu akisin ardindan da gönderilebilir.

4. Widget countdown'u lokal hesapla

Backend dakikalik update göndermedigi için widget countdown'u lokal olarak hesaplamalidir.

Onerilen davranis:

  • displayState == countdown ise scheduledStartAt parse edilir
  • creatorName ana katilimci label'i olarak gösterilir
  • streamTitle baslik olarak gösterilir
  • statusText countdown ustu veya alti sabit aciklama metni olarak gösterilir
  • subtitle ikincil yardimci metin olarak gösterilir
  • kalan sure cihaz saatine gore hesaplanır
  • countdown UI saniye bazli veya dakika bazli guncellenebilir
  • viewerCount su an backend tarafında sabit 128 değeriyle gönderilir
  • tiklandiginda tercihen deepLinkUrl, fallback olarak deepLink kullanılır

Basit parse ornegi:

func parseScheduledStart(_ value: Int?) -> Date? {
    guard let value else { return nil }
    return Date(timeIntervalSince1970: TimeInterval(value))
}

5. Countdown bittiginde lokal davranis

UI onerisi:

  • scheduledStartAt gecildiginde countdown 00:00 seviyesinde sabitlenebilir
  • Tiklandiginda deepLink ile yayin ekranina yonlenilebilir
  • Istiyorsaniz countdown tamamlandiginda minimal bir "basliyor" veya pasif state gösterilebilir
  • Backend cancelled/ready/started/expired anlarinda displayState: ended payload'i gönderebilir; widget bu state'i de decode etmelidir

Backend Lifecycle Haritasi

Backend tarafında Live Activity akisi su noktalarda tetiklenir:

  • Planli yayin olusturulunca:
    • plannedStartDate - 60 dakika için queue job olusturulur
    • yayin 60 dakikadan daha yakin ise start hemen dispatch edilir

Ek davranislar:

  • Creator ve guest hedeflenir
  • Ayni kullanıcı-cihaz-yayin kombinasyonunda duplicate start engellenir
  • Sadece platform = ios, deviceId, FCM token ve liveActivityPushToStartToken olan cihazlar hedeflenir
  • live_stream notification preference kapaliysa activity baslatilmaz
  • Kullanıcı push-to-start token'i gec kaydederse, yakin 60 dakika içindeki uygun scheduled stream'ler için backend backfill dener
  • Runtime token kaydi geldikten sonra backend stream cancelled/ready/started/expired anlarinda end push gönderebilir
  • Stream POST /live-stream/:id/cancel endpoint'i ile iptal edilirse backend ilgili pre-start reminder job'larini, aktif user reminder kayitlarini ve Live Activity end akisina bagli cleanup'i tetikler

Framework Bagimsiz Sequence Diagram

sequenceDiagram
    participant App as iOS App
    participant AK as ActivityKit
    participant API as Backend API
    participant Q as Queue
    participant FCM as FCM/APNs

    App->>AK: pushToStartTokenUpdates dinle
    AK-->>App: push-to-start token
    App->>API: POST /notifications/device-token
    API->>Q: T-60 start job schedule et

    Q-->>API: Job tetiklenir
    API->>FCM: Live Activity start push
    FCM-->>AK: remote start
    AK-->>App: Live Activity gorunur
    App->>API: POST /notifications/live-activity-sessions
    API-->>App: session kaydi tamam
    API->>FCM: gerekiyorsa Live Activity end push
    FCM-->>AK: remote end

Edge Case ve Uyari Notlari

1. Start push'u tamamen sessiz değil

Apple'in ActivityKit push kurallarina gore remote-start payload'inda alert bulunmasi gerekir. Bu nedenle backend start push'unda minimum bir alert icerigi gönderir.

Bu ne anlama gelir:

  • Ayrica klasik bir normal push notification kaydi uretilmez
  • Ancak Live Activity start payload'inin kendi alert'i vardir
  • Alert metni backend tarafında lokalize edilir

2. scheduledStartAt tipi epoch seconds olarak ele alınmali

Backend scheduledStartAt alanini epoch seconds integer olarak gönderir. ContentState içinde bunu Int? alip render asamasinda Date(timeIntervalSince1970:) ile parse etmek daha guvenlidir.

3. deviceId stabil olmali

Farkli deviceId kullanılırsa:

  • backend yeni cihaz gibi davranir
  • duplicate start korumasi zayiflayabilir
  • ayni fiziksel cihaz birden fazla cihaz gibi görünebilir

Bu nedenle cihaz bazli kalici tek bir deviceId seçilip tüm cagrilarda ayni deger kullanılmalıdır.

Pratik oneriler:

  • UIDevice.current.model veya cihaz pazarlama adini deviceId olarak kullanmayin
  • Keychain veya benzeri kalici bir storage'da saklanan UUID kullanın
  • Uygulama acilisinda yeniden generate etmeyin; ayni cihaz için ayni deger kalmalidir

4. Runtime token kaydi zorunludur

Backend'in activity'yi guvenilir sekilde kapatabilmesi için mobil tarafin liveActivityToken bilgisini POST /notifications/live-activity-sessions ile kaydetmesi gerekir. Bu adim atlanirsa backend sadece start yapabilir, end yapamaz.

5. iOS 18 channel akisi bu entegrasyonda yok

Backend su anda ActivityKit channel/broadcast modeli kullanmiyor. Mobil taraf sadece:

  • push-to-start token

akisini implement etmelidir.

Manual QA Checklist

  1. iOS 17.2+ cihazda login ol.
  2. POST /notifications/device-token çağrısı FCM token, deviceId ve liveActivityPushToStartToken ile atiliyor mu kontrol et.
  3. 60 dakikadan fazla ileri tarihli bir scheduled stream olustur.
  4. Zaman yaklastiginda Live Activity remote-start oluyor mu kontrol et.
  5. Widget countdown'u scheduledStartAt ile dogru hesapliyor mu kontrol et.
  6. Remote-start sonrasi POST /notifications/live-activity-sessions çağrısı geliyor mu kontrol et.
  7. Stream cancel/ready/start/expire oldugunda backend end push'u gönderebiliyor mu kontrol et.
  8. Stream cancel endpoint'i cagrildiginda pre-start reminder job'lari ve aktif user reminder kayitlari temizleniyor mu kontrol et.
  9. App restart sonrasi push-to-start token tekrar register ediliyor mu kontrol et.
  10. Ayni yayin için duplicate start olusmuyor mu kontrol et.

Referanslar

On this page