@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.
- package/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/android/src/main/java/com/octovise/sdk/OctoFirebaseMessagingService.kt +72 -0
- package/android/src/main/java/com/octovise/sdk/OctoPushDismissReceiver.kt +54 -0
- package/android/src/main/java/com/octovise/sdk/OctoPushModule.kt +26 -0
- package/android/src/main/java/com/octovise/sdk/OctoPushNotificationBuilder.kt +142 -0
- package/dist/api.d.ts +38 -0
- package/dist/api.js +210 -0
- package/dist/carouselStart.d.ts +1 -0
- package/dist/carouselStart.js +6 -0
- package/dist/components/OctoErrorBoundary.d.ts +15 -0
- package/dist/components/OctoErrorBoundary.js +28 -0
- package/dist/components/OctoImage.d.ts +23 -0
- package/dist/components/OctoImage.js +69 -0
- package/dist/components/OctoMessage.d.ts +9 -0
- package/dist/components/OctoMessage.js +189 -0
- package/dist/components/OctoModal.d.ts +13 -0
- package/dist/components/OctoModal.js +179 -0
- package/dist/components/OctoProvider.d.ts +14 -0
- package/dist/components/OctoProvider.js +92 -0
- package/dist/components/OctoToast.d.ts +13 -0
- package/dist/components/OctoToast.js +392 -0
- package/dist/debug.d.ts +2 -0
- package/dist/debug.js +15 -0
- package/dist/imageFit.d.ts +8 -0
- package/dist/imageFit.js +31 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +195 -0
- package/dist/latency.d.ts +17 -0
- package/dist/latency.js +11 -0
- package/dist/native.d.ts +1 -0
- package/dist/native.js +14 -0
- package/dist/navigation/reactNavigation.d.ts +12 -0
- package/dist/navigation/reactNavigation.js +24 -0
- package/dist/navigation/resolveCTA.d.ts +12 -0
- package/dist/navigation/resolveCTA.js +74 -0
- package/dist/push.d.ts +3 -0
- package/dist/push.js +129 -0
- package/dist/safeNavUrl.d.ts +2 -0
- package/dist/safeNavUrl.js +18 -0
- package/dist/screenStream.d.ts +17 -0
- package/dist/screenStream.js +126 -0
- package/dist/screenTargeting.d.ts +37 -0
- package/dist/screenTargeting.js +154 -0
- package/dist/session.d.ts +2 -0
- package/dist/session.js +22 -0
- package/dist/state.d.ts +8 -0
- package/dist/state.js +53 -0
- package/dist/telemetry.d.ts +6 -0
- package/dist/telemetry.js +65 -0
- package/dist/toastLayout.d.ts +2 -0
- package/dist/toastLayout.js +11 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/ios/OctoviseNotificationExtension/NotificationService.swift +128 -0
- package/ios/OctoviseSDK/OctoPushModule.m +6 -0
- package/ios/OctoviseSDK/OctoPushModule.swift +27 -0
- package/package.json +57 -0
- 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
|
+
[](https://www.npmjs.com/package/@octovise/react-native-sdk)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](#)
|
|
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;
|