@octovise/react-native-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +160 -0
  4. package/android/src/main/java/com/octovise/sdk/OctoFirebaseMessagingService.kt +72 -0
  5. package/android/src/main/java/com/octovise/sdk/OctoPushDismissReceiver.kt +54 -0
  6. package/android/src/main/java/com/octovise/sdk/OctoPushModule.kt +26 -0
  7. package/android/src/main/java/com/octovise/sdk/OctoPushNotificationBuilder.kt +142 -0
  8. package/dist/api.d.ts +38 -0
  9. package/dist/api.js +210 -0
  10. package/dist/carouselStart.d.ts +1 -0
  11. package/dist/carouselStart.js +6 -0
  12. package/dist/components/OctoErrorBoundary.d.ts +15 -0
  13. package/dist/components/OctoErrorBoundary.js +28 -0
  14. package/dist/components/OctoImage.d.ts +23 -0
  15. package/dist/components/OctoImage.js +69 -0
  16. package/dist/components/OctoMessage.d.ts +9 -0
  17. package/dist/components/OctoMessage.js +189 -0
  18. package/dist/components/OctoModal.d.ts +13 -0
  19. package/dist/components/OctoModal.js +179 -0
  20. package/dist/components/OctoProvider.d.ts +14 -0
  21. package/dist/components/OctoProvider.js +92 -0
  22. package/dist/components/OctoToast.d.ts +13 -0
  23. package/dist/components/OctoToast.js +392 -0
  24. package/dist/debug.d.ts +2 -0
  25. package/dist/debug.js +15 -0
  26. package/dist/imageFit.d.ts +8 -0
  27. package/dist/imageFit.js +31 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.js +195 -0
  30. package/dist/latency.d.ts +17 -0
  31. package/dist/latency.js +11 -0
  32. package/dist/native.d.ts +1 -0
  33. package/dist/native.js +14 -0
  34. package/dist/navigation/reactNavigation.d.ts +12 -0
  35. package/dist/navigation/reactNavigation.js +24 -0
  36. package/dist/navigation/resolveCTA.d.ts +12 -0
  37. package/dist/navigation/resolveCTA.js +74 -0
  38. package/dist/push.d.ts +3 -0
  39. package/dist/push.js +129 -0
  40. package/dist/safeNavUrl.d.ts +2 -0
  41. package/dist/safeNavUrl.js +18 -0
  42. package/dist/screenStream.d.ts +17 -0
  43. package/dist/screenStream.js +126 -0
  44. package/dist/screenTargeting.d.ts +37 -0
  45. package/dist/screenTargeting.js +154 -0
  46. package/dist/session.d.ts +2 -0
  47. package/dist/session.js +22 -0
  48. package/dist/state.d.ts +8 -0
  49. package/dist/state.js +53 -0
  50. package/dist/telemetry.d.ts +6 -0
  51. package/dist/telemetry.js +65 -0
  52. package/dist/toastLayout.d.ts +2 -0
  53. package/dist/toastLayout.js +11 -0
  54. package/dist/types.d.ts +78 -0
  55. package/dist/types.js +7 -0
  56. package/dist/version.d.ts +1 -0
  57. package/dist/version.js +4 -0
  58. package/ios/OctoviseNotificationExtension/NotificationService.swift +128 -0
  59. package/ios/OctoviseSDK/OctoPushModule.m +6 -0
  60. package/ios/OctoviseSDK/OctoPushModule.swift +27 -0
  61. package/package.json +57 -0
  62. package/react-native.config.js +16 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ Notas de versión del SDK de Octovise. Versionado con [SemVer](https://semver.org/).
4
+
5
+ Impacto de cada cambio:
6
+
7
+ - **Requiere cambios** — tenés que tocar tu código al actualizar.
8
+ - **Requiere acción** — un paso sin código (p. ej. actualizar una configuración).
9
+ - *(sin etiqueta)* — actualización directa, sin acción.
10
+
11
+ ## [Unreleased]
12
+
13
+ ## [1.0.0] — 2026-07-10
14
+
15
+ - Primera versión estable.
16
+
17
+ ## [0.1.0-beta.1] — 2026-06-07
18
+
19
+ Primer release público beta. **In-app messaging únicamente.** Push notifications quedan para `v1.1`.
20
+
21
+ ### Added
22
+ - `OctoProvider`, `init`, `close`, `forceCheck`, `triggerTestInApp` — superficie pública del SDK
23
+ - Layouts: `modal` y `toast`, con variantes `text` / `image` / `both`
24
+ - `click_mode`: `cta` (default), `full` (toda la card es CTA), `none` (read-only con señal de engagement)
25
+ - Posicionamiento responsive: `position` (desktop) + `mobile_position` (top/center/bottom)
26
+ - Soporte de tema: `colors.primary` / `background` / `text`, `size`, `text_size`, `text_align`
27
+ - Image preload con `Image.prefetch` — skip silencioso si la imagen falla
28
+ - Cooldown de 5 minutos entre `init` consecutivos
29
+ - Dedup por `msg.id` para evitar mostrar el mismo mensaje dos veces seguidas
30
+ - Re-check al volver del foreground (si el cooldown expiró)
31
+ - Tracking de eventos `impression` / `click` / `dismiss` con metadata: `duration_ms`, `time_to_first_interaction_ms`, `page_url`, `device`, `platform`, `image_loaded`, `close_method`
32
+ - `time_to_first_interaction_ms` solo se completa con engagement real (CTA tap o card tap en modos `full` / `none`); dismissing deja la métrica como `null`
33
+ - Botón de cierre con alto contraste (dark backdrop + glyph blanco + sombra) — legible sobre cualquier imagen subyacente
34
+ - Compatible con `keepalive` fetch para que los eventos sobrevivan navegación inmediata post-CTA
35
+
36
+ ### Backend requirements
37
+ - Requiere [`@octovise-sdk-api`](https://sdk.octovise.com.ar) (servidor de Octovise). Ver [Backoffice → Settings → SDK](https://backoffice.octovise.com.ar) para conseguir tu `apiKey`.
38
+ - Para HMAC de userHash, ver la sección de seguridad en el README.
39
+
40
+ ### Known limitations
41
+ - Sin push notifications. El código nativo (iOS Notification Service Extension + Android FirebaseMessagingService) está incluido pero deshabilitado vía `react-native.config.js` (`platforms: null`). Se activa en `v1.1`.
42
+ - Sin React Native New Architecture (TurboModules / Fabric) — no requerido en v1 porque la SDK es JS-only.
43
+ - Sin Expo managed workflow oficialmente probado (debería andar, pero E2E pendiente).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Octovise Argentina S.A.S.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # @octovise/react-native-sdk
2
+
3
+ [![npm](https://img.shields.io/npm/v/@octovise/react-native-sdk?label=npm)](https://www.npmjs.com/package/@octovise/react-native-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ [![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20Android-blue)](#)
6
+
7
+ SDK oficial de **Octovise** para React Native. Renderea mensajes in-app (modal / toast) en la app del cliente y reporta engagement al backend.
8
+
9
+ > **v1.0 incluye solo in-app messaging.** Push notifications llegan en v1.1 — el código nativo ya está en el paquete pero deshabilitado por default.
10
+
11
+ ## Instalación
12
+
13
+ ```bash
14
+ npm install @octovise/react-native-sdk
15
+ # o
16
+ yarn add @octovise/react-native-sdk
17
+ ```
18
+
19
+ **Peer dependencies** (probablemente ya las tenés):
20
+
21
+ ```json
22
+ {
23
+ "react": ">=18",
24
+ "react-native": ">=0.72"
25
+ }
26
+ ```
27
+
28
+ No requiere Firebase, App Group, ni configuración nativa. Es JS-only.
29
+
30
+ ## Quickstart
31
+
32
+ ### 1. Envolvé la app con `<OctoProvider>`
33
+
34
+ ```tsx
35
+ // App.tsx
36
+ import { OctoProvider } from '@octovise/react-native-sdk'
37
+
38
+ export default function App() {
39
+ return (
40
+ <OctoProvider>
41
+ <RestOfYourApp />
42
+ </OctoProvider>
43
+ )
44
+ }
45
+ ```
46
+
47
+ ### 2. Llamá `init()` después del login
48
+
49
+ ```tsx
50
+ import { init } from '@octovise/react-native-sdk'
51
+ import { useNavigation } from '@react-navigation/native'
52
+
53
+ function LoggedInScreen() {
54
+ const nav = useNavigation()
55
+
56
+ useEffect(() => {
57
+ init({
58
+ apiKey: 'pk_live_xxxxxxxxxxxx',
59
+ userId: currentUser.id, // tu ID interno del usuario (number o string)
60
+ onCTA: (url) => nav.navigate(url), // navegación cuando el user toca el CTA
61
+ })
62
+ }, [currentUser])
63
+
64
+ return <YourScreen />
65
+ }
66
+ ```
67
+
68
+ ### 3. Listo
69
+
70
+ El SDK consulta `/check` automáticamente, renderea el mensaje que corresponda y reporta los eventos al backend.
71
+
72
+ ## API
73
+
74
+ ### `init(config)`
75
+
76
+ Arranca la SDK y dispara un primer `/check`. Tiene un cooldown de 5 minutos para evitar requests redundantes en re-renders / re-mounts. Re-chequea automáticamente cuando la app vuelve del background (si pasaron más de 5 min).
77
+
78
+ ```tsx
79
+ type OctoRNConfig = {
80
+ apiKey: string // Backoffice → Settings → SDK
81
+ userId: number | string // tu user_id interno
82
+ userHash?: string // requerido si la SDK key tiene HMAC enforced — HMAC-SHA256(userId, signing_secret)
83
+ baseUrl?: string // default: https://sdk-api.octovise.com.ar
84
+ onCTA?: (url: string) => void // navegación al tap del CTA
85
+ registerPushToken?: boolean // v1.1+ — default: false (in-app only)
86
+ }
87
+ ```
88
+
89
+ ### `close()`
90
+
91
+ Cierra cualquier mensaje on-screen y resetea el cooldown. Llamar al logout.
92
+
93
+ ### `forceCheck({ ignoreDedup? })`
94
+
95
+ Dispara un `/check` saltando el cooldown. Pensado para acciones explícitas del usuario (botón "Actualizar mensajes"). En producción típica, no la necesitás — `init()` + el listener de foreground alcanzan.
96
+
97
+ ### `triggerTestInApp()`
98
+
99
+ Para QA. Pide al backend que encole una campaña de prueba para el usuario actual y la muestra inmediatamente. **No usar en producción.**
100
+
101
+ ## Tipos de mensaje
102
+
103
+ El backend determina qué se renderea — vos no construís los mensajes desde el SDK. Pero estos son los layouts que tu Backoffice puede configurar:
104
+
105
+ | Layout | Cuándo |
106
+ |---|---|
107
+ | `modal` | Centrado, fondo oscuro, requiere atención. Para promos importantes |
108
+ | `toast` | Esquina/borde, discreto. Para tips, recordatorios, novedades |
109
+
110
+ Cada uno soporta variantes con/sin imagen, modos de tap, posición desktop y mobile, colores custom, etc. Todo se configura en el Backoffice → Campañas → In-App.
111
+
112
+ ## Modos de click
113
+
114
+ | `click_mode` | UI | Tap en card | Caso de uso |
115
+ |---|---|---|---|
116
+ | `cta` (default) | Botón CTA visible | Ignorado | Promos clásicas con botón claro |
117
+ | `full` | Sin botón | Dispara CTA + navega | Banner-style card que ES el CTA |
118
+ | `none` | Sin botón, sin CTA destino | Registra engagement (no navega) | Anuncios informativos con tracking |
119
+
120
+ ## Métricas reportadas
121
+
122
+ Cada interacción genera un evento que el SDK manda al backend (con `fetch(..., { keepalive: true })`, así sobrevive navegaciones inmediatas post-CTA):
123
+
124
+ - `impression` — al mostrarse el mensaje
125
+ - `click` — al tocar el CTA (o card en modo `full`/`none`)
126
+ - `dismiss` — al cerrar (X / "Ahora no" / backdrop)
127
+
128
+ Metadata incluida: `duration_ms`, `time_to_first_interaction_ms` (`null` si solo cerró), `device`, `platform` (ios/android), `image_loaded`, `close_method`.
129
+
130
+ ## Cooldown y dedup
131
+
132
+ - **Cooldown de init**: ignora `init()` consecutivos dentro de 5 minutos. Re-mounts y re-renders son seguros.
133
+ - **Dedup por mensaje**: el mismo `msg.id` no se muestra dos veces consecutivas, incluso si `/check` lo devuelve de nuevo.
134
+ - **Foreground listener**: cuando la app vuelve del background y pasaron >5 min desde el último init, re-chequea automáticamente.
135
+
136
+ ## Privacidad
137
+
138
+ El SDK colecta:
139
+ - ID interno de usuario (provisto por vos)
140
+ - URL/page de la app donde se mostró el mensaje (si la pasás)
141
+ - Tipo de device (ios/android) — no IDFA / Advertising ID
142
+ - Tiempos de interacción
143
+
144
+ NO colecta: ubicación, contactos, biometría, IDs de publicidad, ni nada que requiera permisos especiales del sistema operativo.
145
+
146
+ ## Roadmap
147
+
148
+ - **v1.1** — Push notifications (Firebase + Apple Push). Activable con `registerPushToken: true`. Requiere setup adicional del cliente.
149
+ - **v1.2** — Soporte para New Architecture (TurboModules/Fabric).
150
+ - **v2** — Encuestas, NPS inline, deep-link routing avanzado.
151
+
152
+ ## Soporte
153
+
154
+ Para issues con la SDK, problemas de integración o bugs: [GitHub Issues](https://github.com/maurogallardo-cell/octovise-react-native-sdk/issues).
155
+
156
+ Para temas del producto Octovise (configurar campañas, dudas de billing, etc.): [Backoffice → Soporte](https://backoffice.octovise.com.ar).
157
+
158
+ ## License
159
+
160
+ [MIT](LICENSE) © 2026 Octovise Argentina S.A.S.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Firebase messaging service that intercepts data-only messages from Octovise.
3
+ * Builds custom notifications and tracks "received" events.
4
+ *
5
+ * Non-Octovise messages are ignored (the client's own service handles them).
6
+ * This service runs even when the app is killed — it's a native Android service.
7
+ */
8
+
9
+ package com.octovise.sdk
10
+
11
+ import com.google.firebase.messaging.FirebaseMessagingService
12
+ import com.google.firebase.messaging.RemoteMessage
13
+ import java.net.HttpURLConnection
14
+ import java.net.URL
15
+ import org.json.JSONObject
16
+
17
+ class OctoFirebaseMessagingService : FirebaseMessagingService() {
18
+
19
+ override fun onMessageReceived(remoteMessage: RemoteMessage) {
20
+ val data = remoteMessage.data
21
+ if (data["source"] != "octovise") {
22
+ // Not ours — let the default handler or client's service deal with it
23
+ super.onMessageReceived(remoteMessage)
24
+ return
25
+ }
26
+
27
+ // Build and show the custom notification
28
+ val builder = OctoPushNotificationBuilder(applicationContext)
29
+ builder.build(data)
30
+
31
+ // Track "received" event (fire-and-forget, runs on background thread)
32
+ trackReceived(data)
33
+ }
34
+
35
+ private fun trackReceived(data: Map<String, String>) {
36
+ Thread {
37
+ try {
38
+ // Read base URL and API key from SharedPreferences (set by RN init())
39
+ val prefs = applicationContext.getSharedPreferences("octovise_sdk", MODE_PRIVATE)
40
+ val baseUrl = prefs.getString("base_url", null) ?: return@Thread
41
+ val apiKey = prefs.getString("api_key", null) ?: return@Thread
42
+ val userId = prefs.getString("user_id", null) ?: return@Thread
43
+
44
+ val sendId = data["send_id"] ?: return@Thread
45
+
46
+ val payload = JSONObject().apply {
47
+ put("user_id", userId)
48
+ put("send_id", sendId.toIntOrNull() ?: 0)
49
+ put("event", "received")
50
+ put("meta", JSONObject().apply {
51
+ put("device", "mobile")
52
+ put("platform", "android")
53
+ })
54
+ }
55
+
56
+ val url = URL("$baseUrl/event")
57
+ val conn = url.openConnection() as HttpURLConnection
58
+ conn.requestMethod = "POST"
59
+ conn.setRequestProperty("Content-Type", "application/json")
60
+ conn.setRequestProperty("X-API-Key", apiKey)
61
+ conn.connectTimeout = 5000
62
+ conn.readTimeout = 5000
63
+ conn.doOutput = true
64
+ conn.outputStream.write(payload.toString().toByteArray())
65
+ conn.responseCode // trigger the request
66
+ conn.disconnect()
67
+ } catch (_: Exception) {
68
+ // Fire-and-forget — if tracking fails, the notification still shows
69
+ }
70
+ }.start()
71
+ }
72
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Broadcast receiver that fires when the user swipes away an Octovise notification.
3
+ * Tracks "dismiss" event via the SDK API.
4
+ */
5
+
6
+ package com.octovise.sdk
7
+
8
+ import android.content.BroadcastReceiver
9
+ import android.content.Context
10
+ import android.content.Intent
11
+ import java.net.HttpURLConnection
12
+ import java.net.URL
13
+ import org.json.JSONObject
14
+
15
+ class OctoPushDismissReceiver : BroadcastReceiver() {
16
+
17
+ override fun onReceive(context: Context, intent: Intent) {
18
+ val sendId = intent.getStringExtra("octovise_send_id") ?: return
19
+
20
+ Thread {
21
+ try {
22
+ val prefs = context.getSharedPreferences("octovise_sdk", Context.MODE_PRIVATE)
23
+ val baseUrl = prefs.getString("base_url", null) ?: return@Thread
24
+ val apiKey = prefs.getString("api_key", null) ?: return@Thread
25
+ val userId = prefs.getString("user_id", null) ?: return@Thread
26
+
27
+ val payload = JSONObject().apply {
28
+ put("user_id", userId)
29
+ put("send_id", sendId.toIntOrNull() ?: 0)
30
+ put("event", "dismiss")
31
+ put("meta", JSONObject().apply {
32
+ put("device", "mobile")
33
+ put("platform", "android")
34
+ put("close_method", "swipe")
35
+ })
36
+ }
37
+
38
+ val url = URL("$baseUrl/event")
39
+ val conn = url.openConnection() as HttpURLConnection
40
+ conn.requestMethod = "POST"
41
+ conn.setRequestProperty("Content-Type", "application/json")
42
+ conn.setRequestProperty("X-API-Key", apiKey)
43
+ conn.connectTimeout = 5000
44
+ conn.readTimeout = 5000
45
+ conn.doOutput = true
46
+ conn.outputStream.write(payload.toString().toByteArray())
47
+ conn.responseCode
48
+ conn.disconnect()
49
+ } catch (_: Exception) {
50
+ // Fire-and-forget
51
+ }
52
+ }.start()
53
+ }
54
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * React Native bridge module for Android.
3
+ * Persists SDK config (apiKey, userId, baseUrl) to SharedPreferences
4
+ * so the native FirebaseMessagingService can access them when JS isn't running.
5
+ */
6
+
7
+ package com.octovise.sdk
8
+
9
+ import com.facebook.react.bridge.ReactApplicationContext
10
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
11
+ import com.facebook.react.bridge.ReactMethod
12
+
13
+ class OctoPushModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
14
+
15
+ override fun getName(): String = "OctoPushModule"
16
+
17
+ @ReactMethod
18
+ fun configure(apiKey: String, userId: String, baseUrl: String) {
19
+ val prefs = reactApplicationContext.getSharedPreferences("octovise_sdk", 0)
20
+ prefs.edit()
21
+ .putString("api_key", apiKey)
22
+ .putString("user_id", userId)
23
+ .putString("base_url", baseUrl)
24
+ .apply()
25
+ }
26
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Builds custom notifications for Octovise data-only push messages.
3
+ * Supports two layouts: inline (thumbnail) and hero (BigPictureStyle).
4
+ *
5
+ * Called by FirebaseMessagingService when a data message with
6
+ * source=octovise arrives — regardless of app state (foreground/background/killed).
7
+ */
8
+
9
+ package com.octovise.sdk
10
+
11
+ import android.app.NotificationChannel
12
+ import android.app.NotificationManager
13
+ import android.app.PendingIntent
14
+ import android.content.Context
15
+ import android.content.Intent
16
+ import android.graphics.Bitmap
17
+ import android.graphics.BitmapFactory
18
+ import android.os.Build
19
+ import androidx.core.app.NotificationCompat
20
+ import java.net.URL
21
+
22
+ class OctoPushNotificationBuilder(private val context: Context) {
23
+
24
+ companion object {
25
+ private const val CHANNEL_ID_DEFAULT = "octovise_marketing"
26
+ private const val CHANNEL_NAME = "Octovise"
27
+ }
28
+
29
+ fun build(data: Map<String, String>) {
30
+ val title = data["title"] ?: return
31
+ val body = data["body"] ?: return
32
+ val sendId = data["send_id"] ?: "0"
33
+ val imageUrl = data["image_url"]
34
+ val imageLayout = data["image_layout"] ?: "hero"
35
+ val deepLink = data["deep_link"]
36
+ val channelId = data["channel_id"] ?: CHANNEL_ID_DEFAULT
37
+ val collapseKey = data["collapse_key"]
38
+ val campaignId = data["campaign_id"] ?: "0"
39
+
40
+ ensureChannel(channelId)
41
+
42
+ // Download image (if present) on the background thread
43
+ val imageBitmap = imageUrl?.let { downloadImage(it) }
44
+
45
+ // Build tap intent — launches the app's main activity with deep link as extra
46
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
47
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
48
+ putExtra("octovise_send_id", sendId)
49
+ putExtra("octovise_deep_link", deepLink ?: "")
50
+ putExtra("octovise_source", "octovise")
51
+ }
52
+
53
+ val pendingIntent = PendingIntent.getActivity(
54
+ context,
55
+ sendId.hashCode(),
56
+ launchIntent,
57
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
58
+ )
59
+
60
+ // Build dismiss intent for tracking
61
+ val dismissIntent = Intent(context, OctoPushDismissReceiver::class.java).apply {
62
+ putExtra("octovise_send_id", sendId)
63
+ }
64
+ val dismissPendingIntent = PendingIntent.getBroadcast(
65
+ context,
66
+ sendId.hashCode() + 1,
67
+ dismissIntent,
68
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
69
+ )
70
+
71
+ val builder = NotificationCompat.Builder(context, channelId)
72
+ .setSmallIcon(getSmallIcon())
73
+ .setContentTitle(title)
74
+ .setContentText(body)
75
+ .setAutoCancel(true)
76
+ .setContentIntent(pendingIntent)
77
+ .setDeleteIntent(dismissPendingIntent)
78
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
79
+
80
+ if (collapseKey != null) {
81
+ builder.setGroup(collapseKey)
82
+ }
83
+
84
+ // Apply image layout
85
+ if (imageBitmap != null) {
86
+ when (imageLayout) {
87
+ "inline" -> {
88
+ // Thumbnail to the right of title/body
89
+ builder.setLargeIcon(imageBitmap)
90
+ }
91
+ "hero" -> {
92
+ // Large image below text (always expanded)
93
+ builder.setStyle(
94
+ NotificationCompat.BigPictureStyle()
95
+ .bigPicture(imageBitmap)
96
+ .bigLargeIcon(null as Bitmap?)
97
+ )
98
+ builder.setLargeIcon(imageBitmap)
99
+ }
100
+ }
101
+ }
102
+
103
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
104
+ notificationManager.notify(sendId.hashCode(), builder.build())
105
+ }
106
+
107
+ private fun ensureChannel(channelId: String) {
108
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109
+ val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
110
+ if (manager.getNotificationChannel(channelId) == null) {
111
+ val channel = NotificationChannel(
112
+ channelId,
113
+ CHANNEL_NAME,
114
+ NotificationManager.IMPORTANCE_HIGH,
115
+ ).apply {
116
+ description = "Octovise marketing notifications"
117
+ enableVibration(true)
118
+ }
119
+ manager.createNotificationChannel(channel)
120
+ }
121
+ }
122
+ }
123
+
124
+ private fun downloadImage(url: String): Bitmap? {
125
+ return try {
126
+ val connection = URL(url).openConnection()
127
+ connection.connectTimeout = 5000
128
+ connection.readTimeout = 5000
129
+ BitmapFactory.decodeStream(connection.getInputStream())
130
+ } catch (e: Exception) {
131
+ null
132
+ }
133
+ }
134
+
135
+ private fun getSmallIcon(): Int {
136
+ // Try the app's notification icon, fall back to app icon
137
+ val resId = context.resources.getIdentifier(
138
+ "ic_notification", "drawable", context.packageName,
139
+ )
140
+ return if (resId != 0) resId else context.applicationInfo.icon
141
+ }
142
+ }
package/dist/api.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { InAppMessage, EventType, EventMeta } from './types';
2
+ export declare const DEFAULT_BASE_URL = "https://sdk-api.octovise.com.ar";
3
+ export declare function configure(key: string, userId: number | string, userHash?: string, url?: string, appVer?: string): void;
4
+ export declare function buildTelemetryContext(): Record<string, string>;
5
+ export declare function check(sendId?: number): Promise<InAppMessage | null>;
6
+ export interface RuleEntry {
7
+ id: string;
8
+ send_id: number;
9
+ target_screens: number[] | null;
10
+ content: InAppMessage;
11
+ }
12
+ export interface RulesResponse {
13
+ rules: RuleEntry[];
14
+ routes: Array<Record<string, unknown>>;
15
+ }
16
+ export declare function fetchRules(): Promise<RulesResponse | null>;
17
+ export interface ScreenViewWire {
18
+ screen_id: number;
19
+ entity_type: string | null;
20
+ entity_id: number | null;
21
+ reference: string | null;
22
+ session_id: string;
23
+ ts: string;
24
+ }
25
+ export declare function trackScreenViews(views: ScreenViewWire[]): Promise<boolean>;
26
+ export declare function setEventObserver(cb?: (event: EventType, meta: Partial<EventMeta>, sendId: number) => void): void;
27
+ export declare function trackEvent(sendId: number, event: EventType, meta: Partial<EventMeta>): void;
28
+ export interface TelemetryEvent {
29
+ kind: 'error' | 'metric';
30
+ name: string;
31
+ value?: number;
32
+ meta?: Record<string, unknown>;
33
+ }
34
+ export declare function sendTelemetry(events: TelemetryEvent[]): void;
35
+ export declare function testInApp(): Promise<boolean>;
36
+ export declare function registerDevice(fcmToken: string): Promise<void>;
37
+ export declare function getUserId(): string;
38
+ export declare function getBaseUrl(): string;