vkdeploy-push 1.0.0 → 1.1.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/README.md +55 -1
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
- package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/9.2.0/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +53 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/vkdeploy/push/VkDeployMonitorModule.kt +197 -0
- package/android/src/main/java/com/vkdeploy/push/VkDeployMonitorPackage.kt +40 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +54 -1
- package/dist/monitoring.d.ts +18 -0
- package/dist/monitoring.js +118 -0
- package/dist/network.d.ts +18 -2
- package/dist/network.js +65 -8
- package/dist/runtime.d.ts +15 -1
- package/dist/runtime.js +47 -34
- package/dist/types.d.ts +43 -2
- package/dist/utils/functions.js +2 -2
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ if (result.status === 'error') {
|
|
|
52
52
|
|
|
53
53
|
## Configuracao
|
|
54
54
|
|
|
55
|
-
Crie o arquivo `android/app/src/main/assets/
|
|
55
|
+
Crie o arquivo `android/app/src/main/assets/vkdeploy-config.json`:
|
|
56
56
|
|
|
57
57
|
```json
|
|
58
58
|
{
|
|
@@ -96,3 +96,57 @@ setLogger({
|
|
|
96
96
|
error: (message, context) => console.error(message, context),
|
|
97
97
|
});
|
|
98
98
|
```
|
|
99
|
+
|
|
100
|
+
## Monitoramento de falhas (opcional, desligado por padrão)
|
|
101
|
+
|
|
102
|
+
A partir da versão 1.1.0 o SDK pode reportar as saídas anormais do app. O recurso
|
|
103
|
+
nasce **desligado** e só é habilitado pelo painel, por projeto — não há chave de
|
|
104
|
+
ativação no `vkdeploy-config.json` nem no código do app.
|
|
105
|
+
|
|
106
|
+
### O que é coletado
|
|
107
|
+
|
|
108
|
+
A fonte é o `ApplicationExitInfo` do Android (API 30+), lido na abertura seguinte
|
|
109
|
+
à falha. Isso cobre quatro classes de problema que o JavaScript não enxerga:
|
|
110
|
+
|
|
111
|
+
| Motivo | Cobertura |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `CRASH` | exceção Java/Kotlin não tratada |
|
|
114
|
+
| `CRASH_NATIVE` | SIGSEGV e afins, com o tombstone do sistema |
|
|
115
|
+
| `ANR` | dump de todas as threads |
|
|
116
|
+
| `LOW_MEMORY` | processo morto por falta de memória, sem stack trace |
|
|
117
|
+
|
|
118
|
+
Saídas normais (usuário fechou o app, atualização do pacote, limpeza de processo
|
|
119
|
+
em segundo plano) são descartadas no lado nativo.
|
|
120
|
+
|
|
121
|
+
### Como ligar
|
|
122
|
+
|
|
123
|
+
1. Abra o projeto no painel do Versionamento.
|
|
124
|
+
2. Ative a chave **Monitoramento de falhas**.
|
|
125
|
+
|
|
126
|
+
A configuração viaja junto da resposta de `POST /versao`, que o app já consulta a
|
|
127
|
+
cada abertura, então não há requisição adicional. A propagação é imediata para
|
|
128
|
+
quem alterna a chave e de no máximo 5 minutos caso a linha seja alterada direto
|
|
129
|
+
no banco.
|
|
130
|
+
|
|
131
|
+
### Garantias
|
|
132
|
+
|
|
133
|
+
- **Nada é coletado com a chave desligada.** O módulo nativo só é consultado após
|
|
134
|
+
o servidor confirmar que o projeto tem o recurso habilitado, e o servidor
|
|
135
|
+
descarta lotes de projetos desligados.
|
|
136
|
+
- **Ligar a chave não sobe o histórico anterior.** O instante da ativação é usado
|
|
137
|
+
como marca d'água mínima.
|
|
138
|
+
- **Nenhuma falha é perdida por erro de rede.** A marca d'água local só avança
|
|
139
|
+
após confirmação do servidor; um envio que falha é repetido na abertura
|
|
140
|
+
seguinte, e o servidor deduplica pela chave `(projeto, deviceId, ocorridoEm)`.
|
|
141
|
+
- **O monitoramento nunca bloqueia a atualização.** Qualquer erro de coleta ou
|
|
142
|
+
envio é registrado no logger e ignorado.
|
|
143
|
+
|
|
144
|
+
### Requisito de build
|
|
145
|
+
|
|
146
|
+
Esta versão adiciona código nativo. Código nativo **não é entregue por bundle
|
|
147
|
+
OTA**: após atualizar para a 1.1.0 é preciso gerar e publicar um APK novo. Em
|
|
148
|
+
apps que atualizaram apenas o bundle, o SDK detecta a ausência do módulo nativo,
|
|
149
|
+
registra um aviso e segue funcionando sem monitoramento.
|
|
150
|
+
|
|
151
|
+
Dispositivos abaixo do Android 11 não expõem o histórico de saídas do sistema e
|
|
152
|
+
ficam sem esta camada de cobertura.
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
// O app consumidor define estas versoes em rootProject.ext. Os valores de
|
|
3
|
+
// fallback existem apenas para quem compila a biblioteca isolada.
|
|
4
|
+
ext.getExtOrDefault = { name, fallback ->
|
|
5
|
+
rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
repositories {
|
|
9
|
+
google()
|
|
10
|
+
mavenCentral()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion', '2.0.21')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
20
|
+
|
|
21
|
+
android {
|
|
22
|
+
namespace "com.vkdeploy.push"
|
|
23
|
+
compileSdk getExtOrDefault('compileSdkVersion', 35)
|
|
24
|
+
|
|
25
|
+
defaultConfig {
|
|
26
|
+
minSdk getExtOrDefault('minSdkVersion', 21)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
compileOptions {
|
|
30
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
31
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
kotlinOptions {
|
|
35
|
+
jvmTarget = "17"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lintOptions {
|
|
39
|
+
abortOnError false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
repositories {
|
|
44
|
+
google()
|
|
45
|
+
mavenCentral()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dependencies {
|
|
49
|
+
// Sem versao: o plugin Gradle do React Native resolve o artefato correto
|
|
50
|
+
// a partir da versao de react-native usada pelo app consumidor.
|
|
51
|
+
implementation "com.facebook.react:react-android"
|
|
52
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${getExtOrDefault('kotlinVersion', '2.0.21')}"
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
package com.vkdeploy.push
|
|
2
|
+
|
|
3
|
+
import android.annotation.TargetApi
|
|
4
|
+
import android.app.ActivityManager
|
|
5
|
+
import android.app.ApplicationExitInfo
|
|
6
|
+
import android.content.Context
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import com.facebook.react.bridge.Arguments
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
12
|
+
import com.facebook.react.bridge.ReactMethod
|
|
13
|
+
import com.facebook.react.bridge.WritableArray
|
|
14
|
+
import com.facebook.react.bridge.WritableMap
|
|
15
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Le as saidas anormais do proprio processo registradas pelo sistema.
|
|
19
|
+
*
|
|
20
|
+
* O Android mantem um historico curto de como cada processo do app terminou
|
|
21
|
+
* (ActivityManager.getHistoricalProcessExitReasons, API 30+). Consultar esse
|
|
22
|
+
* historico na abertura seguinte cobre, sem instalar nenhum handler, quatro
|
|
23
|
+
* classes de falha que o JavaScript nao enxerga:
|
|
24
|
+
*
|
|
25
|
+
* - crash de Java/Kotlin em modulo nativo;
|
|
26
|
+
* - crash nativo (SIGSEGV e afins), com o tombstone completo;
|
|
27
|
+
* - ANR, com o dump de todas as threads;
|
|
28
|
+
* - morte por falta de memoria, que nao gera stack trace em lugar nenhum e
|
|
29
|
+
* costuma ser relatada pelo usuario como "o app fechou sozinho".
|
|
30
|
+
*
|
|
31
|
+
* Nada e coletado enquanto o monitoramento estiver desligado: o lado JavaScript
|
|
32
|
+
* so chama este modulo depois de o servidor confirmar que o projeto tem o
|
|
33
|
+
* recurso habilitado.
|
|
34
|
+
*/
|
|
35
|
+
@ReactModule(name = VkDeployMonitorModule.NAME)
|
|
36
|
+
class VkDeployMonitorModule(
|
|
37
|
+
reactContext: ReactApplicationContext,
|
|
38
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
39
|
+
|
|
40
|
+
override fun getName(): String = NAME
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Retorna as saidas anormais ocorridas depois de [desdeTimestamp].
|
|
44
|
+
*
|
|
45
|
+
* @param desdeTimestamp marca d'agua em epoch ms; saidas anteriores ou iguais
|
|
46
|
+
* sao descartadas. O lado JavaScript guarda esse valor e o avanca somente
|
|
47
|
+
* apos um envio confirmado, o que evita tanto perda quanto duplicata.
|
|
48
|
+
* @param maximo teto de itens lidos do historico do sistema.
|
|
49
|
+
*/
|
|
50
|
+
@ReactMethod
|
|
51
|
+
fun coletaSaidasAnormais(desdeTimestamp: Double, maximo: Int, promise: Promise) {
|
|
52
|
+
try {
|
|
53
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
54
|
+
// Abaixo da API 30 a API nao existe. Devolver lista vazia mantem o
|
|
55
|
+
// fluxo do SDK identico e deixa a decisao de cobertura para a
|
|
56
|
+
// proxima camada (UncaughtExceptionHandler), que roda em qualquer
|
|
57
|
+
// versao.
|
|
58
|
+
promise.resolve(Arguments.createArray())
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
promise.resolve(leHistorico(desdeTimestamp.toLong(), maximo))
|
|
63
|
+
} catch (erro: Throwable) {
|
|
64
|
+
promise.reject("EXIT_INFO_ERRO", erro.message, erro)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Informa se este dispositivo consegue fornecer o historico de saidas.
|
|
70
|
+
*/
|
|
71
|
+
@ReactMethod
|
|
72
|
+
fun suportaExitInfo(promise: Promise) {
|
|
73
|
+
promise.resolve(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@TargetApi(Build.VERSION_CODES.R)
|
|
77
|
+
private fun leHistorico(desde: Long, maximo: Int): WritableArray {
|
|
78
|
+
val resultado = Arguments.createArray()
|
|
79
|
+
val contexto = reactApplicationContext
|
|
80
|
+
val activityManager = contexto.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
|
81
|
+
?: return resultado
|
|
82
|
+
|
|
83
|
+
val limite = maximo.coerceIn(1, MAX_ITENS)
|
|
84
|
+
|
|
85
|
+
val historico = activityManager.getHistoricalProcessExitReasons(
|
|
86
|
+
contexto.packageName,
|
|
87
|
+
PID_TODOS,
|
|
88
|
+
limite,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
for (saida in historico) {
|
|
92
|
+
if (saida.timestamp <= desde) {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (saida.reason !in MOTIVOS_RELEVANTES) {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
resultado.pushMap(mapeia(saida))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return resultado
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@TargetApi(Build.VERSION_CODES.R)
|
|
107
|
+
private fun mapeia(saida: ApplicationExitInfo): WritableMap {
|
|
108
|
+
val mapa = Arguments.createMap()
|
|
109
|
+
|
|
110
|
+
mapa.putInt("reason", saida.reason)
|
|
111
|
+
mapa.putString("reasonLabel", rotuloMotivo(saida.reason))
|
|
112
|
+
mapa.putString("descricao", saida.description)
|
|
113
|
+
mapa.putInt("importancia", saida.importance)
|
|
114
|
+
mapa.putString("importanciaLabel", rotuloImportancia(saida.importance))
|
|
115
|
+
// pss e rss vem em kB.
|
|
116
|
+
mapa.putDouble("pss", saida.pss.toDouble())
|
|
117
|
+
mapa.putDouble("rss", saida.rss.toDouble())
|
|
118
|
+
mapa.putDouble("ocorridoEm", saida.timestamp.toDouble())
|
|
119
|
+
mapa.putString("trace", leTrace(saida))
|
|
120
|
+
mapa.putString("osVersion", Build.VERSION.RELEASE)
|
|
121
|
+
mapa.putString("modelo", Build.MODEL)
|
|
122
|
+
mapa.putString("fabricante", Build.MANUFACTURER)
|
|
123
|
+
|
|
124
|
+
return mapa
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Le o trace anexado a saida, quando existe.
|
|
129
|
+
*
|
|
130
|
+
* O sistema so anexa trace para ANR (dump das threads) e para crash nativo
|
|
131
|
+
* (tombstone). Em REASON_CRASH o resumo da excecao vem apenas em
|
|
132
|
+
* [ApplicationExitInfo.getDescription], e o retorno aqui e nulo.
|
|
133
|
+
*
|
|
134
|
+
* O tombstone pode passar de centenas de kB, entao a leitura e truncada: as
|
|
135
|
+
* primeiras linhas concentram o sinal (sinal recebido, endereco de falha e
|
|
136
|
+
* a pilha da thread que morreu).
|
|
137
|
+
*/
|
|
138
|
+
@TargetApi(Build.VERSION_CODES.R)
|
|
139
|
+
private fun leTrace(saida: ApplicationExitInfo): String? {
|
|
140
|
+
return try {
|
|
141
|
+
saida.traceInputStream?.use { entrada ->
|
|
142
|
+
val buffer = ByteArray(MAX_TRACE_BYTES)
|
|
143
|
+
var lidos = 0
|
|
144
|
+
|
|
145
|
+
while (lidos < MAX_TRACE_BYTES) {
|
|
146
|
+
val n = entrada.read(buffer, lidos, MAX_TRACE_BYTES - lidos)
|
|
147
|
+
if (n <= 0) break
|
|
148
|
+
lidos += n
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (lidos <= 0) null else String(buffer, 0, lidos, Charsets.UTF_8)
|
|
152
|
+
}
|
|
153
|
+
} catch (erro: Throwable) {
|
|
154
|
+
// Um trace ilegivel nao pode impedir o envio do resto do lote: o
|
|
155
|
+
// motivo, o instante e o uso de memoria ja identificam a falha.
|
|
156
|
+
null
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun rotuloMotivo(reason: Int): String = when (reason) {
|
|
161
|
+
ApplicationExitInfo.REASON_CRASH -> "CRASH"
|
|
162
|
+
ApplicationExitInfo.REASON_CRASH_NATIVE -> "CRASH_NATIVE"
|
|
163
|
+
ApplicationExitInfo.REASON_ANR -> "ANR"
|
|
164
|
+
ApplicationExitInfo.REASON_LOW_MEMORY -> "LOW_MEMORY"
|
|
165
|
+
else -> "DESCONHECIDO_$reason"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private fun rotuloImportancia(importancia: Int): String = when (importancia) {
|
|
169
|
+
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND -> "FOREGROUND"
|
|
170
|
+
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE -> "FOREGROUND_SERVICE"
|
|
171
|
+
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE -> "VISIBLE"
|
|
172
|
+
ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE -> "PERCEPTIBLE"
|
|
173
|
+
ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE -> "SERVICE"
|
|
174
|
+
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED -> "CACHED"
|
|
175
|
+
else -> "OUTRO_$importancia"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
companion object {
|
|
179
|
+
const val NAME = "VkDeployMonitor"
|
|
180
|
+
|
|
181
|
+
private const val PID_TODOS = 0
|
|
182
|
+
private const val MAX_ITENS = 30
|
|
183
|
+
private const val MAX_TRACE_BYTES = 60_000
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Motivos que representam falha. Saidas normais (usuario fechou o app,
|
|
187
|
+
* atualizacao do pacote, limpeza de processo em segundo plano) ficam de
|
|
188
|
+
* fora para nao poluir o painel com ruido que nao pede acao.
|
|
189
|
+
*/
|
|
190
|
+
private val MOTIVOS_RELEVANTES = setOf(
|
|
191
|
+
ApplicationExitInfo.REASON_CRASH,
|
|
192
|
+
ApplicationExitInfo.REASON_CRASH_NATIVE,
|
|
193
|
+
ApplicationExitInfo.REASON_ANR,
|
|
194
|
+
ApplicationExitInfo.REASON_LOW_MEMORY,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.vkdeploy.push
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registro do modulo nativo junto ao autolinking do React Native.
|
|
12
|
+
*
|
|
13
|
+
* Herda de BaseReactPackage para que a instancia so seja criada quando o modulo
|
|
14
|
+
* for de fato usado. Como o monitoramento nasce desligado, na maioria dos apps
|
|
15
|
+
* este modulo nunca chega a ser instanciado.
|
|
16
|
+
*/
|
|
17
|
+
class VkDeployMonitorPackage : BaseReactPackage() {
|
|
18
|
+
|
|
19
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
|
20
|
+
if (name == VkDeployMonitorModule.NAME) VkDeployMonitorModule(reactContext) else null
|
|
21
|
+
|
|
22
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
|
|
23
|
+
mapOf(
|
|
24
|
+
VkDeployMonitorModule.NAME to ReactModuleInfo(
|
|
25
|
+
VkDeployMonitorModule.NAME,
|
|
26
|
+
VkDeployMonitorModule.NAME,
|
|
27
|
+
false, // canOverrideExistingModule
|
|
28
|
+
false, // needsEagerInit
|
|
29
|
+
false, // isCxxModule
|
|
30
|
+
// Modulo escrito a mao, sem spec de codegen: o React Native o expoe
|
|
31
|
+
// pela camada de interoperabilidade da nova arquitetura.
|
|
32
|
+
false, // isTurboModule
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override fun createViewManagers(
|
|
38
|
+
reactContext: ReactApplicationContext,
|
|
39
|
+
): List<ViewManager<*, *>> = emptyList()
|
|
40
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ declare const vkdeployPush: {
|
|
|
11
11
|
resetLogger: typeof resetLogger;
|
|
12
12
|
};
|
|
13
13
|
export default vkdeployPush;
|
|
14
|
-
export type { AtualizarResult, CheckUpdateResult, ErrorCheckResult, ErrorUpdateResult, LoggerContext, RemoteVersionResponse, UpdateLifecycleCallback, UpdatedCheckResult, UpdatedResult, UpToDateCheckResult, VersaoInfo, VkDeployError, VkDeployErrorCode, VkDeployLogger, } from './types';
|
|
14
|
+
export type { AtualizarResult, CheckUpdateResult, ErrorCheckResult, ErrorUpdateResult, LoggerContext, MonitoramentoConfig, MonitoramentoResult, MonitoramentoStatus, RemoteVersionResponse, SaidaAnormal, UpdateLifecycleCallback, UpdatedCheckResult, UpdatedResult, UpToDateCheckResult, VersaoInfo, VkDeployError, VkDeployErrorCode, VkDeployLogger, } from './types';
|
|
15
15
|
export { resetLogger, setLogger };
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const DBSQL_1 = require("./database/DBSQL");
|
|
|
9
9
|
const logger_1 = require("./logger");
|
|
10
10
|
Object.defineProperty(exports, "setLogger", { enumerable: true, get: function () { return logger_1.setLogger; } });
|
|
11
11
|
Object.defineProperty(exports, "resetLogger", { enumerable: true, get: function () { return logger_1.resetLogger; } });
|
|
12
|
+
const monitoring_1 = require("./monitoring");
|
|
12
13
|
const network_1 = require("./network");
|
|
13
14
|
const runtime_1 = require("./runtime");
|
|
14
15
|
const functions_1 = require("./utils/functions");
|
|
@@ -87,6 +88,18 @@ async function getBuildVersion() {
|
|
|
87
88
|
return (0, logger_1.createError)('INVALID_DEPENDENCY', 'Falha ao executar getVersion() de "react-native-device-info".', cause);
|
|
88
89
|
}
|
|
89
90
|
}
|
|
91
|
+
async function getDeviceId() {
|
|
92
|
+
const deviceInfo = await (0, runtime_1.loadDeviceInfo)();
|
|
93
|
+
if (!deviceInfo.ok) {
|
|
94
|
+
return deviceInfo.error;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return await deviceInfo.module.getUniqueId();
|
|
98
|
+
}
|
|
99
|
+
catch (cause) {
|
|
100
|
+
return (0, logger_1.createError)('INVALID_DEPENDENCY', 'Falha ao executar getUniqueId() de "react-native-device-info".', cause);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
90
103
|
async function downloadBundleArchive(fromUrl, toFile) {
|
|
91
104
|
const rnfs = await (0, runtime_1.loadRNFS)();
|
|
92
105
|
if (!rnfs.ok) {
|
|
@@ -102,6 +115,28 @@ async function downloadBundleArchive(fromUrl, toFile) {
|
|
|
102
115
|
return (0, logger_1.createError)('DOWNLOAD_FAILED', 'Falha ao baixar o bundle remoto.', cause);
|
|
103
116
|
}
|
|
104
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Executa o ciclo de monitoramento sem deixar que ele afete a atualizacao.
|
|
120
|
+
*
|
|
121
|
+
* Monitorar e um recurso acessorio: qualquer falha ao coletar ou enviar precisa
|
|
122
|
+
* ser registrada no log e ignorada, jamais impedir o app de receber um bundle
|
|
123
|
+
* novo. Por isso o resultado e apenas anotado e nenhuma excecao escapa daqui.
|
|
124
|
+
*/
|
|
125
|
+
async function executaMonitoramento(config, dados) {
|
|
126
|
+
try {
|
|
127
|
+
const resultado = await (0, monitoring_1.sincronizaMonitoramento)(API_BASE_URL, config, dados);
|
|
128
|
+
if (resultado.status === 'erro' && resultado.error) {
|
|
129
|
+
(0, logger_1.logError)(resultado.error.message, { code: resultado.error.code });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (resultado.status === 'enviado') {
|
|
133
|
+
(0, logger_1.logInfo)('Monitoramento sincronizado.', { enviados: resultado.enviados });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (cause) {
|
|
137
|
+
(0, logger_1.logError)('Falha inesperada no ciclo de monitoramento.', { cause });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
105
140
|
async function getVersao() {
|
|
106
141
|
return (0, DBSQL_1.buscaVersao)();
|
|
107
142
|
}
|
|
@@ -119,12 +154,30 @@ async function checkUpdate(antes, depois) {
|
|
|
119
154
|
if (typeof buildVersion !== 'string') {
|
|
120
155
|
return toCheckErrorResult(buildVersion, versaoLocal);
|
|
121
156
|
}
|
|
122
|
-
const
|
|
157
|
+
const deviceId = await getDeviceId();
|
|
158
|
+
if (typeof deviceId !== 'string') {
|
|
159
|
+
return toCheckErrorResult(deviceId, versaoLocal);
|
|
160
|
+
}
|
|
161
|
+
const remoteVersion = await (0, network_1.requestRemoteVersion)(API_BASE_URL, projectIdResult.projectId, buildVersion, deviceId);
|
|
123
162
|
if (!remoteVersion.ok) {
|
|
124
163
|
return toCheckErrorResult(remoteVersion.error, versaoLocal);
|
|
125
164
|
}
|
|
165
|
+
// O monitoramento roda antes da decisao sobre atualizar, e nao depois, porque a
|
|
166
|
+
// atualizacao termina com o app sendo reiniciado: deixar o envio para o final
|
|
167
|
+
// faria com que justamente os dispositivos que recebem bundle novo nunca
|
|
168
|
+
// reportassem as falhas da versao anterior.
|
|
169
|
+
await executaMonitoramento(remoteVersion.data.monitoramento, {
|
|
170
|
+
projetoId: projectIdResult.projectId,
|
|
171
|
+
deviceId,
|
|
172
|
+
versaoBuild: buildVersion,
|
|
173
|
+
versaoBundle: versaoLocal.versao,
|
|
174
|
+
});
|
|
126
175
|
const versaoAtual = versaoLocal.versao;
|
|
127
176
|
const versaoRemota = remoteVersion.data.versao;
|
|
177
|
+
if (typeof versaoRemota !== 'number') {
|
|
178
|
+
return toCheckErrorResult((0, logger_1.createError)('INVALID_REMOTE_RESPONSE', 'A resposta da API de versão não contém um campo "versao" válido.'), versaoLocal);
|
|
179
|
+
}
|
|
180
|
+
console.log(`Versão atual: ${versaoAtual}, Versão remota: ${versaoRemota}`);
|
|
128
181
|
if (versaoRemota === versaoAtual) {
|
|
129
182
|
return {
|
|
130
183
|
status: 'up-to-date',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MonitoramentoConfig, MonitoramentoResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Coleta e envia as saidas anormais registradas desde o ultimo ciclo.
|
|
4
|
+
*
|
|
5
|
+
* Executa apenas quando o painel tem o monitoramento ligado para o projeto. A
|
|
6
|
+
* marca d'agua efetiva e a maior entre a ultima saida ja enviada e o instante em
|
|
7
|
+
* que o recurso foi ativado, de modo que ligar a chave comeca a coleta dali para
|
|
8
|
+
* a frente e nunca sobe o historico anterior a ativacao.
|
|
9
|
+
*
|
|
10
|
+
* A marca d'agua so avanca apos confirmacao do servidor: se o envio falhar, o
|
|
11
|
+
* mesmo lote e tentado de novo na proxima abertura do app.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sincronizaMonitoramento(apiBaseUrl: string, config: MonitoramentoConfig | undefined, dados: {
|
|
14
|
+
projetoId: string;
|
|
15
|
+
deviceId: string;
|
|
16
|
+
versaoBuild: string;
|
|
17
|
+
versaoBundle: number;
|
|
18
|
+
}): Promise<MonitoramentoResult>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sincronizaMonitoramento = sincronizaMonitoramento;
|
|
4
|
+
const logger_1 = require("./logger");
|
|
5
|
+
const network_1 = require("./network");
|
|
6
|
+
const runtime_1 = require("./runtime");
|
|
7
|
+
const ARQUIVO_MARCA_DAGUA = 'vkdeploy-monitor.json';
|
|
8
|
+
/**
|
|
9
|
+
* Teto de saidas lidas por execucao.
|
|
10
|
+
*
|
|
11
|
+
* O proprio Android guarda cerca de 16 saidas por processo, entao este valor nao
|
|
12
|
+
* costuma ser alcancado; ele limita o tamanho do lote em dispositivos que
|
|
13
|
+
* acumularam historico sem conseguir enviar.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_SAIDAS_POR_EXECUCAO = 25;
|
|
16
|
+
function caminhoMarcaDagua(documentDirectoryPath) {
|
|
17
|
+
return `${documentDirectoryPath}/${ARQUIVO_MARCA_DAGUA}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Le o instante da ultima saida anormal ja enviada com sucesso.
|
|
21
|
+
*
|
|
22
|
+
* Retorna 0 quando o arquivo nao existe ou esta corrompido, o que faz o proximo
|
|
23
|
+
* ciclo reenviar o que ainda estiver no historico do sistema. O indice unico do
|
|
24
|
+
* servidor absorve a repeticao, entao errar para o lado do reenvio e seguro,
|
|
25
|
+
* enquanto errar para o lado de pular perderia a falha de forma definitiva.
|
|
26
|
+
*/
|
|
27
|
+
async function leMarcaDagua() {
|
|
28
|
+
const rnfs = await (0, runtime_1.loadRNFS)();
|
|
29
|
+
if (!rnfs.ok) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const caminho = caminhoMarcaDagua(rnfs.module.DocumentDirectoryPath);
|
|
34
|
+
if (!(await rnfs.module.exists(caminho))) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
const conteudo = await rnfs.module.readFile(caminho, 'utf8');
|
|
38
|
+
const dados = JSON.parse(conteudo);
|
|
39
|
+
return typeof dados.ultimaSaidaEnviada === 'number' && Number.isFinite(dados.ultimaSaidaEnviada)
|
|
40
|
+
? dados.ultimaSaidaEnviada
|
|
41
|
+
: 0;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function salvaMarcaDagua(valor) {
|
|
48
|
+
const rnfs = await (0, runtime_1.loadRNFS)();
|
|
49
|
+
if (!rnfs.ok) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await rnfs.module.writeFile(caminhoMarcaDagua(rnfs.module.DocumentDirectoryPath), JSON.stringify({ ultimaSaidaEnviada: valor }), 'utf8');
|
|
54
|
+
}
|
|
55
|
+
catch (cause) {
|
|
56
|
+
// Falhar ao gravar a marca d'agua apenas provoca reenvio no proximo ciclo,
|
|
57
|
+
// que o servidor deduplica. Nao ha motivo para interromper o fluxo.
|
|
58
|
+
(0, logger_1.logWarn)('Nao foi possivel salvar a marca d\'agua do monitoramento.', { cause });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Coleta e envia as saidas anormais registradas desde o ultimo ciclo.
|
|
63
|
+
*
|
|
64
|
+
* Executa apenas quando o painel tem o monitoramento ligado para o projeto. A
|
|
65
|
+
* marca d'agua efetiva e a maior entre a ultima saida ja enviada e o instante em
|
|
66
|
+
* que o recurso foi ativado, de modo que ligar a chave comeca a coleta dali para
|
|
67
|
+
* a frente e nunca sobe o historico anterior a ativacao.
|
|
68
|
+
*
|
|
69
|
+
* A marca d'agua so avanca apos confirmacao do servidor: se o envio falhar, o
|
|
70
|
+
* mesmo lote e tentado de novo na proxima abertura do app.
|
|
71
|
+
*/
|
|
72
|
+
async function sincronizaMonitoramento(apiBaseUrl, config, dados) {
|
|
73
|
+
var _a;
|
|
74
|
+
if (!(config === null || config === void 0 ? void 0 : config.ativo)) {
|
|
75
|
+
return { status: 'desligado', enviados: 0 };
|
|
76
|
+
}
|
|
77
|
+
const nativo = await (0, runtime_1.loadMonitorNative)();
|
|
78
|
+
if (!nativo.ok) {
|
|
79
|
+
return { status: 'nao-suportado', enviados: 0, error: nativo.error };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
if (!(await nativo.module.suportaExitInfo())) {
|
|
83
|
+
// Dispositivo abaixo do Android 11. A cobertura desta camada depende da
|
|
84
|
+
// API de historico de saidas, que nao existe nessas versoes.
|
|
85
|
+
return { status: 'nao-suportado', enviados: 0 };
|
|
86
|
+
}
|
|
87
|
+
const marcaLocal = await leMarcaDagua();
|
|
88
|
+
const marca = Math.max(marcaLocal, (_a = config.ativadoEm) !== null && _a !== void 0 ? _a : 0);
|
|
89
|
+
const saidas = await nativo.module.coletaSaidasAnormais(marca, MAX_SAIDAS_POR_EXECUCAO);
|
|
90
|
+
if (!Array.isArray(saidas) || saidas.length === 0) {
|
|
91
|
+
return { status: 'sem-novidades', enviados: 0 };
|
|
92
|
+
}
|
|
93
|
+
const envio = await (0, network_1.enviarSaidasAnormais)(apiBaseUrl, {
|
|
94
|
+
projetoId: dados.projetoId,
|
|
95
|
+
deviceId: dados.deviceId,
|
|
96
|
+
versaoBuild: dados.versaoBuild,
|
|
97
|
+
versaoBundle: dados.versaoBundle,
|
|
98
|
+
saidas,
|
|
99
|
+
});
|
|
100
|
+
if (!envio.ok) {
|
|
101
|
+
return { status: 'erro', enviados: 0, error: envio.error };
|
|
102
|
+
}
|
|
103
|
+
const maisRecente = saidas.reduce((maior, saida) => (saida.ocorridoEm > maior ? saida.ocorridoEm : maior), marca);
|
|
104
|
+
await salvaMarcaDagua(maisRecente);
|
|
105
|
+
(0, logger_1.logInfo)('Saidas anormais enviadas.', {
|
|
106
|
+
quantidade: saidas.length,
|
|
107
|
+
gravados: envio.data.gravados,
|
|
108
|
+
});
|
|
109
|
+
return { status: 'enviado', enviados: saidas.length };
|
|
110
|
+
}
|
|
111
|
+
catch (cause) {
|
|
112
|
+
return {
|
|
113
|
+
status: 'erro',
|
|
114
|
+
enviados: 0,
|
|
115
|
+
error: (0, logger_1.createError)('MONITOR_COLLECT_FAILED', 'Falha ao coletar as saidas anormais do dispositivo.', cause),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
package/dist/network.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RemoteVersionResponse, VkDeployError } from './types';
|
|
1
|
+
import type { RemoteVersionResponse, SaidaAnormal, VkDeployError } from './types';
|
|
2
2
|
type Result<T> = {
|
|
3
3
|
ok: true;
|
|
4
4
|
data: T;
|
|
@@ -6,6 +6,22 @@ type Result<T> = {
|
|
|
6
6
|
ok: false;
|
|
7
7
|
error: VkDeployError;
|
|
8
8
|
};
|
|
9
|
-
export declare function requestRemoteVersion(apiBaseUrl: string, projectId: string, buildVersion: string): Promise<Result<RemoteVersionResponse>>;
|
|
9
|
+
export declare function requestRemoteVersion(apiBaseUrl: string, projectId: string, buildVersion: string, deviceId: string): Promise<Result<RemoteVersionResponse>>;
|
|
10
|
+
/**
|
|
11
|
+
* Envia ao servidor as saidas anormais coletadas neste dispositivo.
|
|
12
|
+
*
|
|
13
|
+
* O servidor confere a chave de monitoramento do projeto antes de gravar, entao
|
|
14
|
+
* um lote enviado por um dispositivo com configuracao desatualizada e descartado
|
|
15
|
+
* do outro lado.
|
|
16
|
+
*/
|
|
17
|
+
export declare function enviarSaidasAnormais(apiBaseUrl: string, payload: {
|
|
18
|
+
projetoId: string;
|
|
19
|
+
deviceId: string;
|
|
20
|
+
versaoBuild: string;
|
|
21
|
+
versaoBundle: number;
|
|
22
|
+
saidas: SaidaAnormal[];
|
|
23
|
+
}): Promise<Result<{
|
|
24
|
+
gravados: number;
|
|
25
|
+
}>>;
|
|
10
26
|
export declare function resolveBundleFileName(bundleUrl: string): Promise<string>;
|
|
11
27
|
export {};
|
package/dist/network.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.requestRemoteVersion = requestRemoteVersion;
|
|
4
|
+
exports.enviarSaidasAnormais = enviarSaidasAnormais;
|
|
4
5
|
exports.resolveBundleFileName = resolveBundleFileName;
|
|
5
6
|
const logger_1 = require("./logger");
|
|
6
|
-
async function requestRemoteVersion(apiBaseUrl, projectId, buildVersion) {
|
|
7
|
+
async function requestRemoteVersion(apiBaseUrl, projectId, buildVersion, deviceId) {
|
|
7
8
|
try {
|
|
8
9
|
const response = await fetch(`${apiBaseUrl}/versao`, {
|
|
9
10
|
method: 'POST',
|
|
@@ -14,6 +15,7 @@ async function requestRemoteVersion(apiBaseUrl, projectId, buildVersion) {
|
|
|
14
15
|
body: JSON.stringify({
|
|
15
16
|
projetoId: projectId,
|
|
16
17
|
versaoBuild: buildVersion,
|
|
18
|
+
deviceId,
|
|
17
19
|
}),
|
|
18
20
|
});
|
|
19
21
|
if (!response.ok) {
|
|
@@ -23,17 +25,17 @@ async function requestRemoteVersion(apiBaseUrl, projectId, buildVersion) {
|
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
const data = (await response.json());
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
28
|
+
// A validacao de "versao" acontece em checkUpdate, e nao aqui. Um projeto sem
|
|
29
|
+
// versao ativa publicada recebe uma resposta sem esse campo, e mesmo assim
|
|
30
|
+
// precisa ter a configuracao de monitoramento lida: caso contrario ligar a
|
|
31
|
+
// chave no painel nao surtiria efeito nesses projetos.
|
|
32
|
+
const versao = typeof data.versao === 'number' && !Number.isNaN(data.versao) ? data.versao : undefined;
|
|
32
33
|
return {
|
|
33
34
|
ok: true,
|
|
34
35
|
data: {
|
|
35
|
-
versao
|
|
36
|
+
versao,
|
|
36
37
|
mandatori: data.mandatori,
|
|
38
|
+
monitoramento: normalizaMonitoramento(data.monitoramento),
|
|
37
39
|
},
|
|
38
40
|
};
|
|
39
41
|
}
|
|
@@ -44,6 +46,61 @@ async function requestRemoteVersion(apiBaseUrl, projectId, buildVersion) {
|
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Normaliza a configuracao de monitoramento vinda do servidor.
|
|
51
|
+
*
|
|
52
|
+
* Qualquer formato inesperado resulta em "desligado": o recurso e opt-in e nao
|
|
53
|
+
* pode ser ativado por acidente a partir de uma resposta malformada.
|
|
54
|
+
*/
|
|
55
|
+
function normalizaMonitoramento(valor) {
|
|
56
|
+
if (!valor || typeof valor !== 'object') {
|
|
57
|
+
return { ativo: false, ativadoEm: null };
|
|
58
|
+
}
|
|
59
|
+
const bruto = valor;
|
|
60
|
+
const ativadoEm = typeof bruto.ativadoEm === 'number' && Number.isFinite(bruto.ativadoEm)
|
|
61
|
+
? bruto.ativadoEm
|
|
62
|
+
: null;
|
|
63
|
+
return {
|
|
64
|
+
ativo: bruto.ativo === true,
|
|
65
|
+
ativadoEm,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Envia ao servidor as saidas anormais coletadas neste dispositivo.
|
|
70
|
+
*
|
|
71
|
+
* O servidor confere a chave de monitoramento do projeto antes de gravar, entao
|
|
72
|
+
* um lote enviado por um dispositivo com configuracao desatualizada e descartado
|
|
73
|
+
* do outro lado.
|
|
74
|
+
*/
|
|
75
|
+
async function enviarSaidasAnormais(apiBaseUrl, payload) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(`${apiBaseUrl}/erro`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
Accept: 'application/json',
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify(payload),
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: (0, logger_1.createError)('MONITOR_SEND_FAILED', `Erro ao enviar saidas anormais: HTTP ${response.status}`),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const data = (await response.json());
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
data: { gravados: typeof data.gravados === 'number' ? data.gravados : 0 },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (cause) {
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: (0, logger_1.createError)('MONITOR_SEND_FAILED', 'Falha ao enviar as saidas anormais.', cause),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
47
104
|
async function resolveBundleFileName(bundleUrl) {
|
|
48
105
|
var _a;
|
|
49
106
|
try {
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VkDeployError, VersaoInfo } from './types';
|
|
1
|
+
import type { SaidaAnormal, VkDeployError, VersaoInfo } from './types';
|
|
2
2
|
export type RNFSDirEntry = {
|
|
3
3
|
path: string;
|
|
4
4
|
name: string;
|
|
@@ -37,6 +37,11 @@ type ZipArchiveModule = {
|
|
|
37
37
|
unzip(source: string, target: string): Promise<unknown>;
|
|
38
38
|
};
|
|
39
39
|
export type UnzipFunction = ZipArchiveModule['unzip'];
|
|
40
|
+
/** Superficie do modulo nativo de monitoramento (android/src/main/.../VkDeployMonitorModule.kt). */
|
|
41
|
+
export type MonitorNativeModule = {
|
|
42
|
+
coletaSaidasAnormais(desdeTimestamp: number, maximo: number): Promise<SaidaAnormal[]>;
|
|
43
|
+
suportaExitInfo(): Promise<boolean>;
|
|
44
|
+
};
|
|
40
45
|
type ModuleLoadResult<T> = {
|
|
41
46
|
ok: true;
|
|
42
47
|
module: T;
|
|
@@ -49,5 +54,14 @@ export declare function ensureSupportedPlatform(): Promise<VkDeployError | undef
|
|
|
49
54
|
export declare function loadRNFS(): Promise<ModuleLoadResult<RNFSModule>>;
|
|
50
55
|
export declare function loadDeviceInfo(): Promise<ModuleLoadResult<DeviceInfoModule>>;
|
|
51
56
|
export declare function loadUnzip(): Promise<ModuleLoadResult<UnzipFunction>>;
|
|
57
|
+
/**
|
|
58
|
+
* Carrega o modulo nativo de monitoramento.
|
|
59
|
+
*
|
|
60
|
+
* A ausencia do modulo e um caso esperado, nao um defeito: codigo nativo nao
|
|
61
|
+
* viaja em bundle OTA, entao um app que atualizou o vkdeploy-push por bundle sem
|
|
62
|
+
* gerar um APK novo continua sem ele. Nesse caso o monitoramento fica inativo e
|
|
63
|
+
* o restante do SDK segue funcionando normalmente.
|
|
64
|
+
*/
|
|
65
|
+
export declare function loadMonitorNative(): Promise<ModuleLoadResult<MonitorNativeModule>>;
|
|
52
66
|
export declare function getSafeBundleVersion(): Promise<string>;
|
|
53
67
|
export {};
|
package/dist/runtime.js
CHANGED
|
@@ -1,47 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.buildVersionInfo = buildVersionInfo;
|
|
37
4
|
exports.ensureSupportedPlatform = ensureSupportedPlatform;
|
|
38
5
|
exports.loadRNFS = loadRNFS;
|
|
39
6
|
exports.loadDeviceInfo = loadDeviceInfo;
|
|
40
7
|
exports.loadUnzip = loadUnzip;
|
|
8
|
+
exports.loadMonitorNative = loadMonitorNative;
|
|
41
9
|
exports.getSafeBundleVersion = getSafeBundleVersion;
|
|
42
10
|
const logger_1 = require("./logger");
|
|
43
11
|
const UNKNOWN_BUNDLE_VERSION = '0.0.0';
|
|
44
12
|
const loggedMessages = new Set();
|
|
13
|
+
const MONITOR_NATIVE_NAME = 'VkDeployMonitor';
|
|
45
14
|
function logOnce(key, level, message, details) {
|
|
46
15
|
if (loggedMessages.has(key)) {
|
|
47
16
|
return;
|
|
@@ -57,9 +26,23 @@ function normalizeModule(module) {
|
|
|
57
26
|
var _a;
|
|
58
27
|
return ((_a = module.default) !== null && _a !== void 0 ? _a : module);
|
|
59
28
|
}
|
|
29
|
+
function getModule(moduleName) {
|
|
30
|
+
switch (moduleName) {
|
|
31
|
+
case 'react-native':
|
|
32
|
+
return require('react-native');
|
|
33
|
+
case 'react-native-fs':
|
|
34
|
+
return require('react-native-fs');
|
|
35
|
+
case 'react-native-device-info':
|
|
36
|
+
return require('react-native-device-info');
|
|
37
|
+
case 'react-native-zip-archive':
|
|
38
|
+
return require('react-native-zip-archive');
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`Unsupported module import: ${moduleName}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
60
43
|
async function importModule(moduleName) {
|
|
61
44
|
try {
|
|
62
|
-
const imported = (
|
|
45
|
+
const imported = getModule(moduleName);
|
|
63
46
|
return {
|
|
64
47
|
ok: true,
|
|
65
48
|
module: normalizeModule(imported),
|
|
@@ -154,6 +137,36 @@ async function loadUnzip() {
|
|
|
154
137
|
module: unzip,
|
|
155
138
|
};
|
|
156
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Carrega o modulo nativo de monitoramento.
|
|
142
|
+
*
|
|
143
|
+
* A ausencia do modulo e um caso esperado, nao um defeito: codigo nativo nao
|
|
144
|
+
* viaja em bundle OTA, entao um app que atualizou o vkdeploy-push por bundle sem
|
|
145
|
+
* gerar um APK novo continua sem ele. Nesse caso o monitoramento fica inativo e
|
|
146
|
+
* o restante do SDK segue funcionando normalmente.
|
|
147
|
+
*/
|
|
148
|
+
async function loadMonitorNative() {
|
|
149
|
+
var _a;
|
|
150
|
+
const reactNative = await importModule('react-native');
|
|
151
|
+
if (!reactNative.ok) {
|
|
152
|
+
return reactNative;
|
|
153
|
+
}
|
|
154
|
+
const nativo = (_a = reactNative.module.NativeModules) === null || _a === void 0 ? void 0 : _a[MONITOR_NATIVE_NAME];
|
|
155
|
+
if (!nativo ||
|
|
156
|
+
typeof nativo.coletaSaidasAnormais !== 'function' ||
|
|
157
|
+
typeof nativo.suportaExitInfo !== 'function') {
|
|
158
|
+
const error = (0, logger_1.createError)('MONITOR_UNAVAILABLE', 'O modulo nativo "VkDeployMonitor" nao esta disponivel. Gere um novo APK apos atualizar o vkdeploy-push: codigo nativo nao e entregue por bundle OTA.');
|
|
159
|
+
logOnce('monitor:unavailable', 'warn', error.message, { code: error.code });
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
error,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
ok: true,
|
|
167
|
+
module: nativo,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
157
170
|
async function getSafeBundleVersion() {
|
|
158
171
|
const deviceInfo = await loadDeviceInfo();
|
|
159
172
|
if (!deviceInfo.ok) {
|
package/dist/types.d.ts
CHANGED
|
@@ -8,10 +8,51 @@ export interface VersionamentoConfig {
|
|
|
8
8
|
app_secret: string;
|
|
9
9
|
}
|
|
10
10
|
export interface RemoteVersionResponse {
|
|
11
|
-
versao
|
|
11
|
+
versao?: number;
|
|
12
12
|
mandatori?: boolean;
|
|
13
|
+
monitoramento?: MonitoramentoConfig;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Configuracao de monitoramento entregue pelo servidor junto da resposta de versao.
|
|
17
|
+
*
|
|
18
|
+
* O recurso nasce desligado e so e ligado pelo painel; o app nunca decide isso
|
|
19
|
+
* sozinho e nao ha como habilitar por arquivo de configuracao local.
|
|
20
|
+
*/
|
|
21
|
+
export interface MonitoramentoConfig {
|
|
22
|
+
/** Indica se o projeto tem monitoramento habilitado no painel. */
|
|
23
|
+
ativo: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Epoch ms em que o monitoramento foi ligado, ou null quando desligado.
|
|
26
|
+
*
|
|
27
|
+
* Funciona como marca d'agua minima: saidas anormais anteriores a este instante
|
|
28
|
+
* sao descartadas, de modo que ligar a chave nunca envia o historico de falhas
|
|
29
|
+
* acumulado enquanto o recurso estava desligado.
|
|
30
|
+
*/
|
|
31
|
+
ativadoEm: number | null;
|
|
32
|
+
}
|
|
33
|
+
/** Uma saida anormal do processo, como relatada pelo Android. */
|
|
34
|
+
export interface SaidaAnormal {
|
|
35
|
+
reason: number;
|
|
36
|
+
reasonLabel: string;
|
|
37
|
+
descricao?: string | null;
|
|
38
|
+
importancia?: number;
|
|
39
|
+
importanciaLabel?: string;
|
|
40
|
+
pss?: number;
|
|
41
|
+
rss?: number;
|
|
42
|
+
ocorridoEm: number;
|
|
43
|
+
trace?: string | null;
|
|
44
|
+
osVersion?: string;
|
|
45
|
+
modelo?: string;
|
|
46
|
+
fabricante?: string;
|
|
47
|
+
}
|
|
48
|
+
export type MonitoramentoStatus = 'desligado' | 'nao-suportado' | 'sem-novidades' | 'enviado' | 'erro';
|
|
49
|
+
export interface MonitoramentoResult {
|
|
50
|
+
status: MonitoramentoStatus;
|
|
51
|
+
/** Quantidade de saidas anormais enviadas ao servidor. */
|
|
52
|
+
enviados: number;
|
|
53
|
+
error?: VkDeployError;
|
|
13
54
|
}
|
|
14
|
-
export type VkDeployErrorCode = 'UNSUPPORTED_PLATFORM' | 'MISSING_DEPENDENCY' | 'INVALID_DEPENDENCY' | 'CONFIG_NOT_FOUND' | 'INVALID_CONFIG' | 'REMOTE_REQUEST_FAILED' | 'INVALID_REMOTE_RESPONSE' | 'DOWNLOAD_FAILED' | 'UNZIP_FAILED' | 'FILESYSTEM_ERROR' | 'UNKNOWN_ERROR';
|
|
55
|
+
export type VkDeployErrorCode = 'UNSUPPORTED_PLATFORM' | 'MISSING_DEPENDENCY' | 'INVALID_DEPENDENCY' | 'CONFIG_NOT_FOUND' | 'INVALID_CONFIG' | 'REMOTE_REQUEST_FAILED' | 'INVALID_REMOTE_RESPONSE' | 'DOWNLOAD_FAILED' | 'UNZIP_FAILED' | 'FILESYSTEM_ERROR' | 'MONITOR_UNAVAILABLE' | 'MONITOR_COLLECT_FAILED' | 'MONITOR_SEND_FAILED' | 'UNKNOWN_ERROR';
|
|
15
56
|
export interface VkDeployError {
|
|
16
57
|
code: VkDeployErrorCode;
|
|
17
58
|
message: string;
|
package/dist/utils/functions.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buscProjetoId = buscProjetoId;
|
|
4
4
|
const logger_1 = require("../logger");
|
|
5
5
|
const runtime_1 = require("../runtime");
|
|
6
|
-
const CONFIG_ASSET_PATH = '
|
|
6
|
+
const CONFIG_ASSET_PATH = 'vkdeploy-config.json';
|
|
7
7
|
async function buscProjetoId() {
|
|
8
8
|
var _a;
|
|
9
9
|
const rnfs = await (0, runtime_1.loadRNFS)();
|
|
@@ -17,7 +17,7 @@ async function buscProjetoId() {
|
|
|
17
17
|
if (!exists) {
|
|
18
18
|
return {
|
|
19
19
|
ok: false,
|
|
20
|
-
error: (0, logger_1.createError)('CONFIG_NOT_FOUND', 'Arquivo de configuração não encontrado em android/app/src/main/assets/
|
|
20
|
+
error: (0, logger_1.createError)('CONFIG_NOT_FOUND', 'Arquivo de configuração não encontrado em android/app/src/main/assets/vkdeploy-config.json.'),
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vkdeploy-push",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Biblioteca React Native para versionamento e atualizacao remota de bundle Android.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
+
"android",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
@@ -28,7 +29,10 @@
|
|
|
28
29
|
"android",
|
|
29
30
|
"bundle",
|
|
30
31
|
"ota",
|
|
31
|
-
"update"
|
|
32
|
+
"update",
|
|
33
|
+
"monitoring",
|
|
34
|
+
"crash",
|
|
35
|
+
"anr"
|
|
32
36
|
],
|
|
33
37
|
"license": "MIT",
|
|
34
38
|
"dependencies": {
|