Pular para o conteúdo

Visão geral ​

NottiSDK é o SDK nativo oficial de iOS para notificações push do Notti (APNs). Ele cuida do registro de dispositivo, identidade do usuário, tags, eventos de engajamento (received / opened / clicked), mídia rica em uma Notification Service Extension e mensagens in-app, para que a integração nunca precise montar chamadas REST manualmente contra a API de Devices. Ele é distribuído em dois produtos: NottiSDK para o app e NottiSDKExtension para a Notification Service Extension.

A versão 0.1.0 (lançada em 2026-10-26) é a primeira. Ela porta o react-native-notti 0.8.0 para iOS, com mensagens in-app incluídas, o mesmo contrato com o backend e o mesmo armazenamento no dispositivo. O checklist de dispositivo passou nos aparelhos do mantenedor e a validação da App Store passou; um beta em um app hospedeiro em produção vem a seguir. Veja Limitações conhecidas.

Por padrão, o SDK se comunica com o Notti (https://app.zeepnotti.app), então Notti.initialize só precisa do ID do seu app e da chave de cliente.

Requisitos ​

  • iOS 15.0+.
  • Xcode 15.3 ou mais recente. O target da extensão usa nonisolated(unsafe), que exige Swift 5.10. Apenas o Xcode 26.6 e o 27.1 são testados.
  • Um backend do Notti que aceite o que o SDK envia: v0.10.0 ou mais recente para os campos de perfil do dispositivo, v0.11.0 ou mais recente para o evento opened, v0.23.0 ou mais recente para mensagens in-app e v0.24.0 ou mais recente para a desvinculação no logout (veja Limitações conhecidas). Um backend mais antigo rejeita esses campos ou eventos e o SDK os descarta.
  • Uma conta Apple Developer com push notifications e uma chave APNs configurada no seu App do Notti.
  • Um App do Notti e seus ID do app/chave de cliente - veja Primeiros passos com o Notti para criar um.

Instalação ​

O SDK é distribuído via Swift Package Manager e CocoaPods. Para um app nativo, o caminho recomendado é o Swift Package Manager; o CocoaPods também é suportado.

Swift Package Manager ​

No Xcode: File → Add Package Dependencies… e informe:

https://github.com/zeeplabs/zeep-notti-ios-sdk.git

Use a regra de dependência Up to Next Major Version a partir de 0.1.0.

  • Adicione o produto NottiSDK ao target do seu app.
  • Adicione o produto NottiSDKExtension ao target da sua Notification Service Extension (opcional, para mídia rica).

Em um Package.swift:

swift
.package(url: "https://github.com/zeeplabs/zeep-notti-ios-sdk.git", from: "0.1.0")

CocoaPods ​

ruby
target 'YourApp' do
  pod 'NottiSDK', '~> 0.1'
end

target 'YourNotificationServiceExtension' do
  pod 'NottiSDKExtension', '~> 0.1'
end

Depois rode pod install.

Linkar o NottiSDKExtension no target do app compila, mas não expõe nenhuma API de app. Use o NottiSDK ali. Na extensão, use import NottiSDKExtension, a mesma linha usada com SPM. Os dois pods funcionam com frameworks dinâmicos, frameworks estáticos e bibliotecas estáticas.

Capabilities ​

No target do app, em Signing & Capabilities:

  • Push Notifications
  • Background Modes → Remote notifications (necessário para pushes silenciosos)

A Notification Service Extension não precisa de nenhuma capability própria.

Inicializando e pedindo permissão ​

Chame Notti.initialize uma vez na inicialização com o ID do app e a chave de cliente (nunca a chave REST - veja a Referência da API de Apps para entender o porquê) e depois peça a permissão quando fizer sentido para o seu usuário. requestPermission retorna se o usuário concedeu a permissão.

swift
Notti.initialize(appId: "YOUR_APP_ID", clientKey: "YOUR_CLIENT_KEY")

Task {
    let granted = await Notti.Notifications.requestPermission()
    print("push permission granted: \(granted)")
}

Sem opções, o SDK se comunica com o host de produção https://app.zeepnotti.app. Para usar outro host, passe Notti.Options(baseUrl:). Notti.Options(opensInAppURLs: false) impede o SDK de abrir links web de mensagens in-app (veja Mensagens in-app). O SDK nunca pede permissão de localização.

Identidade e tags ​

login vincula o dispositivo ao ID do seu próprio usuário. E-mail, telefone e tags são opcionais e só são enviados quando você os define. logout limpa o vínculo no dispositivo, envia external_user_id: null para desvinculá-lo no backend e limpa e-mail e telefone; as tags permanecem.

  • A desvinculação fica guardada no dispositivo até o backend confirmá-la: um logout feito offline, antes de Notti.initialize ou cuja requisição falhou é reenviado no próximo registro (um login posterior, feito depois de Notti.initialize, o substitui).
  • Chamadas de login, de tags e de setSubscription feitas antes de Notti.initialize são descartadas, cada uma com um aviso (nível warn): chame-as depois de initialize. logout é a única chamada de identidade mantida quando feita antes de initialize.
  • Depois de initialize e antes do primeiro registro, só a última chamada de login ou logout é enviada.
  • A desvinculação no backend exige o backend do Notti v0.24.0 ou mais recente: veja Limitações conhecidas.
swift
Notti.login("user-123")
Notti.User.setEmail("person@example.invalid")
Notti.User.addTags(["plan": "premium", "locale": "pt-BR"])
Notti.User.removeTag("locale")

Notti.logout()

Outras chamadas: Notti.User.setPhone, clearEmail, clearPhone, addTag, removeTags, Notti.User.setSubscription(_:) e Notti.setLocationSharingEnabled(_:) (desativado por padrão, veja Privacidade, rótulos da App Store e LGPD).

O compartilhamento de localização envia apenas o código do país, resolvido no dispositivo por geocodificação reversa de uma posição que o seu app já tem. O SDK nunca pede permissão de localização; a geocodificação reversa em si é feita pelo serviço da Apple.

Tratando eventos de notificação ​

Os listeners recebem o payload de um push que chegou com o app em execução (received) e de um toque (clicked). O SDK nunca abre a URL de um push: o data do push chega intacto ao seu código e você decide o que um link significa. (Mensagens in-app são diferentes: o SDK abre uma ação urlhttp/https de mensagem in-app e, para um botão push_permission, a página de configurações de notificação do sistema: veja Mensagens in-app.)

swift
let received = Notti.Notifications.addReceivedListener { payload in
    print("received with \(payload.data.count) data keys")
}
let clicked = Notti.Notifications.addClickedListener { payload in
    // `payload.data` is the push data, untouched. Decide what a link means here.
    print("clicked with \(payload.data.count) data keys")
}

// A tap that launched the app is kept until you take it.
if let initial = Notti.Notifications.takeInitialClick() {
    print("launched from a push: \(initial.data.count) data keys")
}

received.remove()
clicked.remove()

Um toque vai para os listeners de clique quando há pelo menos um registrado naquele momento. Um toque que chega sem nenhum listener registrado é guardado, e takeInitialClick() retorna o último toque guardado uma única vez. No UIKit, o toque que abriu o app chega depois que application(_:didFinishLaunchingWithOptions:) retorna, então um listener adicionado ali o recebe ao vivo e takeInitialClick() retorna nil; sem listener ainda, um toque posterior também é retornado por takeInitialClick(). Trate a abertura pelos dois caminhos, o que vier primeiro.

Remova um listener com remove() na assinatura retornada. Notti.addDeviceIdChangedListener chama seu handler na main thread sempre que o ID do dispositivo muda e é removido da mesma forma.

Os eventos de entrega (received, opened, clicked) ficam em fila no dispositivo e são reenviados com backoff (veja a política de retentativas em Limitações conhecidas).

Mensagens in-app ​

Links. Por padrão, o SDK abre no Safari uma ação url de mensagem in-app cujo valor é um link http ou https, depois que o seu listener de clique rodou: não abra o link de novo no listener. Ele nunca abre um deeplink, nem uma url com qualquer outro esquema (tel:, sms:, esquemas próprios): esses ficam com você, pelo listener de clique. Sem listener, um toque nesse botão ainda reporta click ao Notti e fecha a mensagem, nada é aberto e o SDK registra um aviso (nível warn). Para abrir links web por conta própria, passe Notti.Options(opensInAppURLs: false) para Notti.initialize.

As mensagens são criadas no painel do Notti (veja Mensagens in-app) e exigem o backend do Notti v0.23.0 ou mais recente. O SDK pergunta ao backend uma vez por sessão em foreground, depois que este processo registrou o dispositivo, quais mensagens este dispositivo pode ver (POST /v1/apps/<appId>/in-app-messages/eligible). Ele exibe a primeira mensagem session_start imediatamente e mantém as mensagens event em memória até que o seu app reporte um evento correspondente. No máximo uma mensagem fica visível, e cada mensagem aparece no máximo uma vez por sessão. Se ela aparece de novo em uma sessão posterior é decisão do backend: o SDK não guarda estado de "já exibida". O lado do servidor dessa troca está descrito no contrato das mensagens in-app.

swift
// Add it before `Notti.initialize`, so a message shown right after launch already has a listener.
let inAppClicks = Notti.InAppMessages.addClickListener { click in
    // nil for a `push_permission` button and for a button without a valid action.
    guard let action = click.action else { return }
    // `action.value` is the author's link, untouched.
    if action.type == .deeplink {
        // The SDK never opens a deeplink: route it inside the app.
        print("route the deeplink inside the app")
    } else {
        // The SDK opens an http(s) url in Safari after this listener returns: do not open it again.
        print("url click, opened by the SDK")
    }
}

// Shows the oldest waiting message whose trigger is this event name (exact, case-sensitive).
Notti.trackEvent("checkout_completed")

inAppClicks.remove()
  • Notti.trackEvent(_:) faz a correspondência apenas no dispositivo: o nome nunca é enviado, armazenado ou registrado em log. Um nome vazio ou só com espaços não faz nada. Uma chamada antes de Notti.initialize, ou em background depois que uma sessão terminou, é ignorada. Uma chamada depois de Notti.initialize e antes de a primeira sessão abrir é lembrada para essa sessão (até 32 nomes distintos), então initialize seguido de trackEvent na inicialização funciona. Essa ordem na inicialização ainda não foi comprovada em dispositivo.
  • O listener de clique roda na main thread para um toque em um botão, em uma imagem tocável ou no corpo de uma mensagem top ou bottom que tem botões (ele age como o primeiro botão). NottiInAppClick traz messageId, buttonId (image para a imagem) e action: um NottiInAppAction com type (url ou deeplink) e o value intacto, ou nil. Guarde a assinatura retornada e chame remove() para parar.
  • Links web. Com opensInAppURLs verdadeiro (o padrão), depois que os listeners rodaram e a mensagem fechou, o SDK abre uma ação url no navegador externo (UIApplication.open) quando o valor é uma URL http ou https (em qualquer caixa) com host e sem espaços. Um universal link https abre o app correspondente, conforme o iOS decidir. Qualquer outro valor não é aberto e registra uma linha fixa no nível debug; uma abertura que falha não é repetida. O listener recebe todos os cliques de qualquer forma, com o valor intacto, e o SDK nunca registra o link em log. Um deeplink nunca é aberto pelo SDK. Um segundo Notti.initialize com o mesmo ID do app, chave de cliente e URL base e outro opensInAppURLs aplica a opção sem reiniciar o SDK. Todo Notti.initialize válido define a opção com o valor que carrega, como o baseUrl: uma chamada posterior sem opções a reativa, então passe o mesmo Notti.Options em todas as chamadas (em Objective-C, use o seletor opensInAppURLs: todas as vezes).
  • Botões push_permission executam o fluxo de permissão do sistema em vez de um link: o listener recebe o clique com action == nil, a mensagem fecha e então o SDK pede a permissão de push se ela nunca foi pedida, abre as configurações de notificação do app no app Ajustes se a permissão foi negada (iOS 16 e mais recentes; a página de configurações do app no iOS 15) e não faz mais nada se ela já foi concedida. Essa página de configurações é uma constante fixa do sistema, nunca um dado do autor. O backend entrega mensagens push_permission ao iOS 0.1.0 e mais recentes, mas só a um dispositivo que informa sdk_name como ios, e esta versão do SDK ainda não o envia.
  • Eventos. O SDK reporta impression (uma vez por exibição, sempre primeiro), click (com o ID do botão) e dismiss (motivo close_button, swipe, outside_tap ou auto). Eles ficam em fila no dispositivo, são enviados em ordem com um event_id gerado no cliente que sobrevive a retentativas e reinícios, e são descartados após 7 dias; acima de 100 eventos em fila, o mais antigo é descartado.
  • Apresentação. Quatro layouts: barras top e bottom, um card center sobre um fundo escurecido e full dentro da safe area. Cada mensagem tem um controle de fechar (44 pt, rótulo "Close", ou "Fechar" quando o primeiro idioma preferido é português). As barras fecham com um swipe, center com um toque no fundo e full só com o controle de fechar ou um botão. Um dismiss_after_seconds opcional (1 a 300) fecha a mensagem sozinho; a contagem pausa em background.
  • Janela própria. A mensagem aparece em uma UIWindow separada no nível .alert (acima das janelas do seu app, como no react-native-notti) que nunca vira a key window, então não tira o foco do teclado do seu app. Toques fora de uma barra chegam ao seu app. A janela segue o root view controller da sua key window quanto às orientações suportadas e à status bar. Como ela se comporta junto com o teclado, a Central de Controle e o Picture in Picture ainda não foi comprovado em dispositivo.
  • Animação. Uma barra desliza 24 pt a partir da borda enquanto aparece em fade (0,25 s); center e full aparecem em fade (0,25 s); toda saída some em fade em 0,2 s, e os toques passam através dela enquanto isso. Com Reduzir Movimento ativado, entrada e saída são um cross-fade de 0,15 s sem movimento.
  • Somente texto puro. title e body são exibidos caractere por caractere: HTML, Markdown e links não são renderizados. As cores do painel (#RRGGBB) são aplicadas como enviadas; um valor inválido volta ao padrão, e os padrões seguem o modo escuro. O texto escala com o Dynamic Type; textos longos rolam enquanto o controle de fechar e os botões continuam acessíveis.
  • Acessibilidade. O título é um cabeçalho, seguido do corpo e dos botões. center e full são modais para o VoiceOver, recebem o foco e fecham com o gesto de escape (Z com dois dedos) como close_button; as barras são anunciadas e não recebem o foco.
  • Imagens. Apenas imagens https, de no máximo 5 MiB, baixadas sem a sua chave de cliente, token ou cookies. Se a imagem não ficar pronta em 3 segundos, a mensagem aparece sem ela.

Notificações push ricas (Notification Service Extension) ​

Adicione um target de Notification Service Extension, linke o NottiSDKExtension e encaminhe os dois callbacks. A extensão baixa a URL de mídia trazida pelo push (chave notti_image_url, nunca parte de payload.data) e a anexa. A URL precisa ser https, o arquivo ter no máximo 10 MiB e o tipo ser jpg, png, gif, mp4 ou mp3, pelo MIME type ou pela extensão da URL. Um arquivo de tipo desconhecido ainda é tentado como jpg, então pode ou não ser renderizado. O download tem um prazo de 20 segundos; se o tempo acabar, a extensão entrega o conteúdo que já tem.

swift
import NottiSDKExtension
import UserNotifications

@objc(NotificationService)
final class NotificationService: UNNotificationServiceExtension {
    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var bestAttemptContent: UNNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = request.content
        NottiNotificationServiceExtension.didReceive(request, withContentHandler: contentHandler)
    }

    override func serviceExtensionTimeWillExpire() {
        guard let contentHandler else { return }
        NottiNotificationServiceExtension.serviceExtensionTimeWillExpire(
            for: bestAttemptContent, contentHandler: contentHandler
        )
    }
}

A Notification Service Extension não é exercitada no Simulador do iOS pelos testes do próprio SDK - teste mídia rica em um dispositivo físico.

Integração com o delegate e desativação do swizzling ​

Por padrão, o SDK se instala na inicialização e observa os callbacks de push no delegate do seu app e no delegate do UNUserNotificationCenter, então nenhum código de encaminhamento é necessário. Isso funciona com um delegate UIKit e com o @UIApplicationDelegateAdaptor do SwiftUI. Um app SwiftUI sem o adaptor não é suportado pelo swizzling: veja Limitações conhecidas. Quando o app deixa o delegate do UNUserNotificationCenter como nil, o SDK atribui o seu ao fim da inicialização; um delegate que você definir depois é observado, não substituído.

Para desativar isso, adicione esta chave ao Info.plist do seu app. Só um Boolean true conta; uma string ou um número é ignorado e o SDK registra um aviso.

xml
<key>NottiDisableSwizzling</key>
<true/>

Com a chave definida, você mesmo define o delegate do notification center e encaminha estes cinco callbacks:

swift
final class ForwardingAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        return true
    }

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Notti.Notifications.didRegisterForRemoteNotifications(deviceToken: deviceToken)
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        Notti.Notifications.didFailToRegisterForRemoteNotifications(error: error)
    }

    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable: Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        completionHandler(Notti.Notifications.didReceiveRemoteNotification(userInfo))
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        completionHandler(Notti.Notifications.willPresent(notification))
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        Notti.Notifications.didReceive(response)
        completionHandler()
    }
}

Objective-C ​

O SDK pode ser usado em Objective-C pelo mesmo módulo. A classe de entrada é NottiSDK, não Notti, para poder conviver no mesmo app com o react-native-notti, cuja classe de módulo é Notti. As demais classes são NottiUser, NottiNotifications e NottiInAppMessages.

objc
@import NottiSDK;

[NottiSDK initializeWithAppId:@"YOUR_APP_ID" clientKey:@"YOUR_CLIENT_KEY" baseUrl:nil];
// Or, to open In-App web links in your own code instead of the SDK:
// [NottiSDK initializeWithAppId:@"YOUR_APP_ID" clientKey:@"YOUR_CLIENT_KEY" baseUrl:nil opensInAppURLs:NO];
[NottiNotifications requestPermission:^(BOOL granted) { /* ... */ }];
[NottiSDK login:@"user-123"];
[NottiUser addTag:@"plan" value:@"premium"];
NottiSubscription *sub = [NottiNotifications addClickedListener:^(NSDictionary *payload) { /* ... */ }];
[sub remove];

[NottiSDK trackEvent:@"checkout_completed"];
// payload: messageId, buttonId, action (@{@"type": @"url" or @"deeplink", @"value": ...} or NSNull)
NottiSubscription *clicks = [NottiInAppMessages addClickListener:^(NSDictionary *payload) { /* ... */ }];

Este bloco Objective-C não é compilado pela CI do SDK; os snippets Swift desta página são.

Privacidade, rótulos da App Store e LGPD ​

Esta seção descreve o que o SDK faz. Isto não é aconselhamento jurídico e não afirma que o seu uso está em conformidade com a LGPD - valide com seu próprio time jurídico/DPO antes de publicar.

Enviado ao backend do Notti ​

As requisições vão para /v1/apps/<appId>/devices (registro), /v1/apps/<appId>/devices/<deviceId> (toda atualização posterior, então o ID do dispositivo vai no path), /v1/apps/<appId>/notifications/<id>/events, /v1/apps/<appId>/in-app-messages/eligible e /v1/apps/<appId>/in-app-messages/<messageId>/events, autenticadas com a sua chave de cliente. Toda atualização de dispositivo também leva o token de push.

QuandoCampos
Registrotoken (token de push do APNs), platform (ios)
loginexternal_user_id
setEmail, setPhoneemail, phone
addTag, addTags, removeTag, removeTagstags (seus próprios pares chave/valor)
Permissão e setSubscriptionpermission_status, subscribed, last_unsubscribed_at
Perfil do dispositivo, sincronizado quando mudaapp_version, sdk_version, device_os, device_model, timezone_id, language
Sessõesfirst_session_at, last_session_at, session_count, session_time_seconds
Compartilhamento de localização ativado, e o seu app já tem permissão de localizaçãoapenas country (null quando você desativa o compartilhamento)
Um push é recebido, aberto ou clicadodelivery_id, type (received, opened, clicked), token, reportados sob o ID da notificação
Busca de mensagens in-app, uma vez por sessão em foregrounddevice_id, token
Uma mensagem in-app é exibida, tocada ou fechadadevice_id, token, event_id, type (impression, click, dismiss), occurred_at, button_id (só em click, o ID definido pelo autor), reason (só em dismiss, um valor fixo), reportados sob o ID da mensagem

Não coloque dados pessoais em chaves ou valores de tags, a menos que a sua própria base legal de tratamento cubra isso.

Armazenado no dispositivo ​

O SDK guarda seu estado na suíte notti_prefs do UserDefaults, sem criptografia, com os mesmos nomes de chave do react-native-notti 0.8.0. Isso inclui e-mail, telefone, ID de usuário externo e tags. A lista completa de chaves está no README do SDK.

Os logs passam por um único logger, com nível padrão error. Eles nunca contêm e-mail, telefone, token de push, ID do dispositivo, ID de usuário externo ou valores de tags, nem IDs, títulos, corpos, rótulos de botões, links, URLs de imagens de mensagens in-app ou nomes de trackEvent, e os erros são registrados como domain#code.

Privacy manifests ​

O NottiSDK inclui um PrivacyInfo.xcprivacy com NSPrivacyTracking falso e nenhum domínio de rastreamento. Ele declara a API de motivo obrigatório UserDefaults (CA92.1) e estes tipos de dados coletados, todos vinculados ao usuário e nenhum usado para rastreamento: Device ID, User ID, Email Address, Phone Number, Coarse Location, Product Interaction, Other Diagnostic Data e Other Data Types. As finalidades são App Functionality para todos os tipos, exceto Product Interaction, que declara apenas Analytics; Coarse Location declara as duas. O NottiSDKExtension inclui seu próprio manifest, sem dados coletados e sem API de motivo obrigatório. As mensagens in-app não acrescentam tipo de dado: seus eventos impression, click e dismiss são Product Interaction, e o ID do dispositivo e o token de push enviados em cada requisição in-app são Device ID.

Other Diagnostic Data e Other Data Types são a escolha mais próxima para campos que a lista da Apple não nomeia. Confirme-os com o responsável por privacidade.

O que o seu app declara no App Store Connect ​

As respostas de privacidade do seu app precisam cobrir o que o SDK coleta na sua configuração, além da sua própria coleta. Com base no manifest acima: Device ID; User ID se você chamar login; Email e Phone se você os definir; Coarse Location só se você ativar o compartilhamento de localização; Product Interaction; Other Diagnostic Data; e Other Data Types se você definir tags. Vinculado ao usuário: sim. Usado para rastreamento: não.

LGPD ​

Antes de publicar, peça aos seus times jurídico e de privacidade que revisem os dados acima, a base legal, o aviso exibido aos usuários e a retenção e exclusão no seu backend do Notti. Essa revisão inclui as mensagens in-app: cada requisição in-app leva o ID do dispositivo e o token de push, e cada mensagem que o usuário vê, toca ou fecha vira um evento vinculado ao registro do dispositivo. Os nomes de trackEvent ficam no dispositivo.

O SDK não tem API de consentimento na 0.1.0: controle você mesmo quando chamar Notti.initialize e Notti.login.

Limitações conhecidas ​

  • Mídia rica só em dispositivo. A Notification Service Extension não é exercitada no Simulador pelos testes do próprio SDK.
  • logout e novo registro exigem o backend do Notti v0.24.0 ou mais recente. A partir dessa versão, um PATCH com external_user_id: null desvincula o dispositivo, e um registro que omite external_user_id e tags os mantém. Contra um backend mais antigo, a desvinculação responde 2xx mas o backend mantém o valor armazenado, então o dispositivo continua vinculado ao usuário anterior até a próxima inicialização (pushes para esse usuário podem continuar chegando em um dispositivo compartilhado); o SDK conta o 2xx como entregue e não tenta de novo. Um backend mais antigo também reseta external_user_id e tags a cada registro na inicialização, então chame login e addTags de novo a cada inicialização. O react-native-notti 0.8.0 tem os mesmos limites em um backend mais antigo.
  • As tags sobrevivem ao logout. logout não limpa as tags no backend; limpe-as você mesmo (removeTags) se elas descrevem o usuário.
  • Backups podem levar a identidade do dispositivo para outro aparelho. A suíte notti_prefs faz parte do backup do dispositivo iOS. Restaurá-la em outro telefone pode direcionar atualizações para o registro do dispositivo original antes de o SDK se registrar de novo. Se isso importa para o seu app, exclua-a do backup.
  • SwiftUI sem @UIApplicationDelegateAdaptor. O SDK não faz swizzling do app delegate interno do SwiftUI. Ele registra um aviso (nível warn, então você não vê nada no nível padrão error) e não recebe o token do APNs, então o dispositivo nunca se registra pelo SDK. Use @UIApplicationDelegateAdaptor com o seu próprio delegate, ou defina NottiDisableSwizzling e encaminhe as cinco chamadas você mesmo.
  • Delegates só com o método legado não recebem received de push silencioso. Um app delegate que implementa só application(_:didReceiveRemoteNotification:) (não a variante com fetchCompletionHandler) não recebe uma substituição, então um push silencioso não gera evento received. Implemente o método moderno ou use a API manual.
  • Observação dupla. Se o seu delegate sobrescreve um callback de push e chama super, o SDK pode observar o mesmo callback duas vezes.
  • Sem received para pushes visíveis sem notification_id. Um push de outro remetente, ou sem esse campo, não é reportado como received.
  • Mensagens in-app em dispositivo. Renderização, VoiceOver, o nível da janela em relação ao teclado, à Central de Controle e ao Picture in Picture, apps legados sem scenes, multitarefa no iPad, o salto para as configurações do push_permission e a ordem de initialize, trackEvent e da primeira sessão na inicialização ainda não foram comprovados separadamente em dispositivo.
  • Mensagens push_permission ainda não são entregues a este SDK. O backend tem um mínimo por SDK para elas (iOS 0.1.0), mas o aplica ao SDK que o dispositivo informa em sdk_name. Esta versão do SDK não o informa, então o backend trata o dispositivo como React Native e oculta a mensagem até uma versão do SDK enviar sdk_name.
  • Um 403 in-app não refaz o registro. Em um 403 das rotas in-app, o SDK registra um aviso por sessão e rota, para de buscar mensagens pelo resto da sessão (a próxima sessão em foreground busca de novo) e mantém os eventos em fila, que são reenviados no próximo envio. Ele não refaz o registro como o contrato do servidor sugere.
  • Sem API de consentimento na 0.1.0. Não há chamada para segurar a coleta até o usuário consentir. Controle você mesmo Notti.initialize e Notti.login, e chame login depois de initialize: antes disso, o login é descartado (um aviso).
  • Dados pessoais no UserDefaults. E-mail, telefone, ID de usuário externo e tags são armazenados sem criptografia na suíte notti_prefs.
  • Sem Retry-After. Uma requisição é tentada até 5 vezes, com esperas de 2, 4, 8 e 16 segundos entre as tentativas. Só erros de rede, 5xx, 408, 429 e (no registro) um 2xx sem um corpo de dispositivo válido são repetidos; os demais 4xx são definitivos. Eventos que esgotam as tentativas continuam em fila e são tentados de novo em um envio posterior. Um header Retry-After é ignorado.

Código-fonte e changelog ​