@pushmesh/sdk 0.7.2

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/android/build.gradle +82 -0
  4. package/android/src/main/AndroidManifest.xml +69 -0
  5. package/android/src/main/java/io/pushmesh/sdk/PushMeshClickActivity.kt +124 -0
  6. package/android/src/main/java/io/pushmesh/sdk/PushMeshFirebaseModule.kt +512 -0
  7. package/android/src/main/java/io/pushmesh/sdk/PushMeshFirebasePackage.kt +20 -0
  8. package/android/src/main/java/io/pushmesh/sdk/PushMeshMessageBuffer.kt +53 -0
  9. package/android/src/main/java/io/pushmesh/sdk/PushMeshMessageRouter.kt +477 -0
  10. package/android/src/main/java/io/pushmesh/sdk/PushMeshMessagingReceiver.kt +77 -0
  11. package/android/src/main/java/io/pushmesh/sdk/PushMeshMessagingService.kt +88 -0
  12. package/dist/cjs/boot.d.ts +7 -0
  13. package/dist/cjs/boot.js +1 -0
  14. package/dist/cjs/compat.d.ts +63 -0
  15. package/dist/cjs/compat.js +1 -0
  16. package/dist/cjs/doctor.d.ts +27 -0
  17. package/dist/cjs/doctor.js +2 -0
  18. package/dist/cjs/events.d.ts +21 -0
  19. package/dist/cjs/events.js +1 -0
  20. package/dist/cjs/http.d.ts +20 -0
  21. package/dist/cjs/http.js +1 -0
  22. package/dist/cjs/inapp/SkeletonShimmer.d.ts +1 -0
  23. package/dist/cjs/inapp/SkeletonShimmer.js +1 -0
  24. package/dist/cjs/inapp/auto.d.ts +1 -0
  25. package/dist/cjs/inapp/auto.js +1 -0
  26. package/dist/cjs/inapp/components.d.ts +14 -0
  27. package/dist/cjs/inapp/components.js +1 -0
  28. package/dist/cjs/inapp/engine.d.ts +1 -0
  29. package/dist/cjs/inapp/engine.js +1 -0
  30. package/dist/cjs/inapp/imageCache.d.ts +1 -0
  31. package/dist/cjs/inapp/imageCache.js +1 -0
  32. package/dist/cjs/inapp/index.d.ts +20 -0
  33. package/dist/cjs/inapp/index.js +1 -0
  34. package/dist/cjs/inapp/lifecycle.d.ts +1 -0
  35. package/dist/cjs/inapp/lifecycle.js +1 -0
  36. package/dist/cjs/inapp/normalize.d.ts +1 -0
  37. package/dist/cjs/inapp/normalize.js +1 -0
  38. package/dist/cjs/inapp/registro.d.ts +1 -0
  39. package/dist/cjs/inapp/registro.js +1 -0
  40. package/dist/cjs/inapp/sessao.d.ts +1 -0
  41. package/dist/cjs/inapp/sessao.js +1 -0
  42. package/dist/cjs/index.d.ts +118 -0
  43. package/dist/cjs/index.js +1 -0
  44. package/dist/cjs/perfil.d.ts +52 -0
  45. package/dist/cjs/perfil.js +1 -0
  46. package/dist/cjs/permissions.d.ts +54 -0
  47. package/dist/cjs/permissions.js +1 -0
  48. package/dist/cjs/players.d.ts +20 -0
  49. package/dist/cjs/players.js +1 -0
  50. package/dist/cjs/queue.d.ts +1 -0
  51. package/dist/cjs/queue.js +1 -0
  52. package/dist/cjs/receipts.d.ts +32 -0
  53. package/dist/cjs/receipts.js +1 -0
  54. package/dist/cjs/state.d.ts +21 -0
  55. package/dist/cjs/state.js +1 -0
  56. package/dist/cjs/storage.d.ts +1 -0
  57. package/dist/cjs/storage.js +1 -0
  58. package/dist/cjs/tags.d.ts +13 -0
  59. package/dist/cjs/tags.js +1 -0
  60. package/dist/cjs/triggers.d.ts +22 -0
  61. package/dist/cjs/triggers.js +1 -0
  62. package/dist/cjs/types.d.ts +258 -0
  63. package/dist/cjs/types.js +1 -0
  64. package/dist/cjs/zeroconfig.d.ts +45 -0
  65. package/dist/cjs/zeroconfig.js +1 -0
  66. package/ios/NotificationService/LEIA-ME.md +105 -0
  67. package/ios/NotificationService/PushMeshNotificationService.swift +384 -0
  68. package/ios/PushMeshApns.swift +746 -0
  69. package/ios/PushMeshApnsModule.m +76 -0
  70. package/ios/PushMeshSwizzleRegistro.swift +141 -0
  71. package/package.json +113 -0
  72. package/pushmesh-sdk.podspec +32 -0
  73. package/react-native.config.js +17 -0
@@ -0,0 +1,76 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+ #import <UIKit/UIKit.h>
4
+
5
+ /**
6
+ * Bridge old-arch (interop) do módulo Swift PushMeshApns.
7
+ * Ver ios/PushMeshApns.swift — APNs direto, sem Firebase.
8
+ */
9
+ /**
10
+ * Gancho de CARREGAMENTO: instala o wrapper do delegate de notificações assim
11
+ * que o app termina de abrir.
12
+ *
13
+ * O UNUserNotificationCenter só entrega o `didReceive` (o toque que abriu o
14
+ * app) para quem JÁ é delegate quando o launch termina. Como o módulo React
15
+ * nasce depois disso, o toque com o app FECHADO se perdia em silêncio: clique
16
+ * não contabilizava e a pm_url não abria. Observar
17
+ * UIApplicationDidFinishLaunchingNotification a partir do +load é o momento
18
+ * mais cedo possível sem exigir uma linha no AppDelegate do cliente — a
19
+ * premissa zero-config continua de pé.
20
+ */
21
+ @interface PushMeshApnsBootstrap : NSObject
22
+ @end
23
+
24
+ @implementation PushMeshApnsBootstrap
25
+
26
+ + (void)load {
27
+ // O gancho do setDelegate: entra JÁ (antes de qualquer delegate existir).
28
+ {
29
+ Class cls = NSClassFromString(@"PushMeshApns");
30
+ SEL sel = NSSelectorFromString(@"instalarGanchoSetDelegate");
31
+ if (cls && [cls respondsToSelector:sel]) {
32
+ #pragma clang diagnostic push
33
+ #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
34
+ [cls performSelector:sel];
35
+ #pragma clang diagnostic pop
36
+ }
37
+ }
38
+ [[NSNotificationCenter defaultCenter]
39
+ addObserverForName:UIApplicationDidFinishLaunchingNotification
40
+ object:nil
41
+ queue:[NSOperationQueue mainQueue]
42
+ usingBlock:^(NSNotification *_Nonnull note) {
43
+ Class cls = NSClassFromString(@"PushMeshApns");
44
+ SEL sel = NSSelectorFromString(@"instalarSwizzleDelegate");
45
+ if (cls && [cls respondsToSelector:sel]) {
46
+ // performSelector em classe conhecida e seletor sem argumentos
47
+ #pragma clang diagnostic push
48
+ #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
49
+ [cls performSelector:sel];
50
+ #pragma clang diagnostic pop
51
+ }
52
+ }];
53
+ }
54
+
55
+ @end
56
+
57
+ @interface RCT_EXTERN_MODULE(PushMeshApns, RCTEventEmitter)
58
+
59
+ RCT_EXTERN_METHOD(register)
60
+
61
+ RCT_EXTERN_METHOD(getToken:(RCTPromiseResolveBlock)resolve
62
+ rejecter:(RCTPromiseRejectBlock)reject)
63
+
64
+ RCT_EXTERN_METHOD(perfilDispositivo:(RCTPromiseResolveBlock)resolve
65
+ rejecter:(RCTPromiseRejectBlock)reject)
66
+
67
+ RCT_EXTERN_METHOD(getPendingClick:(RCTPromiseResolveBlock)resolve
68
+ rejecter:(RCTPromiseRejectBlock)reject)
69
+
70
+ // Atalho direto para a tela de Notificações do app (iOS 16+). Sem esta linha o
71
+ // método existe no Swift e o JS não o enxerga — o SDK cairia sempre no
72
+ // fallback e o conserto não teria efeito nenhum.
73
+ RCT_EXTERN_METHOD(abrirAjustesDeNotificacao:(RCTPromiseResolveBlock)resolve
74
+ rejecter:(RCTPromiseRejectBlock)reject)
75
+
76
+ @end
@@ -0,0 +1,141 @@
1
+ import Foundation
2
+ import ObjectiveC
3
+
4
+ /**
5
+ * REGISTRO DE SWIZZLE — a memória de "quem era o IMP original", POR CLASSE.
6
+ *
7
+ * POR QUE ISTO EXISTE (lei nascida de defeito real, 2026-08): o SDK embrulha
8
+ * os callbacks do delegate (APNs e UNUserNotificationCenter) por method
9
+ * swizzling — é o preço do zero-config. Guardar o IMP original numa ÚNICA
10
+ * variável estática funciona enquanto existe UMA classe de delegate na vida do
11
+ * processo. No mundo real — migração saindo de outro SDK de push, que é o
12
+ * caminho de produção deste SDK — o delegate TROCA de classe, e aí a estática
13
+ * única vira bomba:
14
+ *
15
+ * A→B→A ........: embrulhamos a classe A; depois a B (a estática passa a
16
+ * apontar para o original de B); o host volta a pôr um
17
+ * delegate da classe A. Ao re-embrulhar A, o "original" que
18
+ * o runtime devolve É O NOSSO PRÓPRIO BLOCO, instalado ali
19
+ * na primeira vez. Encadear nele = chamar a si mesmo =
20
+ * recursão infinita = stack overflow no app do cliente, no
21
+ * primeiro push que chegar.
22
+ * encadeamento ..: o SDK que substitui o delegate costuma GUARDAR o anterior
23
+ * e encaminhar a ele (padrão de mercado). Com a estática
24
+ * única, o bloco instalado na classe A encadeava o IMP de B
25
+ * — corpo de outra classe rodando com `self` de A (layout de
26
+ * ivars errado) — e o ciclo B→A→B se fechava.
27
+ *
28
+ * Daí as duas leis desta casa:
29
+ * 1. o original é lembrado por PAR (classe, seletor) e resolvido pela classe
30
+ * do ALVO em tempo de CHAMADA — nunca por "quem foi embrulhado por
31
+ * último";
32
+ * 2. um IMP NOSSO jamais é capturado como "original": todo IMP que
33
+ * instalamos fica marcado, então auto-encadeamento é impossível por
34
+ * construção, não por disciplina de quem escreve o próximo bloco.
35
+ *
36
+ * Aqui NÃO entra UIKit nem React de propósito: só runtime ObjC. É o que
37
+ * permite compilar esta lógica sozinha e exercitá-la de verdade num binário de
38
+ * teste — ver `__tests__/ios/cenario_swizzle.swift`, rodado pelo
39
+ * `__tests__/ios-swizzle.test.ts` (o SDK não tem harness nativo).
40
+ */
41
+ enum PushMeshSwizzleRegistro {
42
+
43
+ /// Par (classe, seletor): a identidade do que embrulhamos. A classe entra
44
+ /// como ObjectIdentifier (endereço do metatipo) e o seletor como texto —
45
+ /// ambos estáveis durante todo o processo.
46
+ private struct Chave: Hashable {
47
+ let classe: ObjectIdentifier
48
+ let seletor: String
49
+
50
+ init(_ classe: AnyClass, _ seletor: Selector) {
51
+ self.classe = ObjectIdentifier(classe)
52
+ self.seletor = NSStringFromSelector(seletor)
53
+ }
54
+ }
55
+
56
+ /// A instalação roda na main queue, mas a LEITURA acontece dentro do bloco
57
+ /// swizzlado — e nada garante que todo SDK vizinho respeite a main thread.
58
+ /// Dicionário/Set corrompido é crash pior que o que estamos consertando.
59
+ private static let trava = NSLock()
60
+ private static var originais: [Chave: IMP] = [:]
61
+ private static var embrulhadas: Set<Chave> = []
62
+ /// Endereços dos IMPs que NÓS instalamos (lei 2).
63
+ private static var nossos: Set<UInt> = []
64
+
65
+ private static func endereco(_ imp: IMP) -> UInt {
66
+ UInt(bitPattern: unsafeBitCast(imp, to: UnsafeRawPointer.self))
67
+ }
68
+
69
+ /// true quando o IMP é um bloco NOSSO — o que jamais pode virar "original".
70
+ static func ehNosso(_ imp: IMP?) -> Bool {
71
+ guard let imp = imp else { return false }
72
+ trava.lock()
73
+ defer { trava.unlock() }
74
+ return nossos.contains(endereco(imp))
75
+ }
76
+
77
+ /// true quando este par (classe, seletor) já está embrulhado — é o que torna
78
+ /// o A→B→A um no-op em vez de uma re-captura venenosa.
79
+ static func jaEmbrulhada(_ classe: AnyClass, _ seletor: Selector) -> Bool {
80
+ trava.lock()
81
+ defer { trava.unlock() }
82
+ return embrulhadas.contains(Chave(classe, seletor))
83
+ }
84
+
85
+ /**
86
+ * Instala `nossoImp` na classe SEMPRE preservando o original DELA:
87
+ * - a classe define o método → troca o IMP, original capturado antes;
88
+ * - a classe só HERDA o método → adiciona override na subclasse (a
89
+ * superclasse NÃO é tocada) e o original capturado é o IMP herdado;
90
+ * - ninguém responde → só adiciona (nada a encadear);
91
+ * - o IMP que está lá é NOSSO (A→B→A, ou subclasse que herdou nosso bloco)
92
+ * → não captura nada: o original verdadeiro já está no registro, sob esta
93
+ * classe ou sob alguma ancestral.
94
+ *
95
+ * Idempotente por par (classe, seletor): devolve `false` quando não havia
96
+ * nada a fazer, para quem chama não logar embrulho que não aconteceu.
97
+ */
98
+ @discardableResult
99
+ static func instalar(classe: AnyClass, seletor: Selector, tipos: String, nossoImp: IMP) -> Bool {
100
+ trava.lock()
101
+ defer { trava.unlock() }
102
+
103
+ let chave = Chave(classe, seletor)
104
+ guard !embrulhadas.contains(chave) else { return false }
105
+
106
+ // marca ANTES de capturar: se o que está instalado for nosso, a captura
107
+ // abaixo já sabe reconhecer.
108
+ nossos.insert(endereco(nossoImp))
109
+
110
+ let atual: IMP? = class_respondsToSelector(classe, seletor)
111
+ ? class_getMethodImplementation(classe, seletor)
112
+ : nil
113
+ if let atual = atual, !nossos.contains(endereco(atual)) {
114
+ originais[chave] = atual
115
+ }
116
+
117
+ if !class_addMethod(classe, seletor, nossoImp, tipos),
118
+ let metodo = class_getInstanceMethod(classe, seletor) {
119
+ method_setImplementation(metodo, nossoImp)
120
+ }
121
+ embrulhadas.insert(chave)
122
+ return true
123
+ }
124
+
125
+ /**
126
+ * Resolução PURA a partir da classe em que o bloco foi INSTALADO (o chamador
127
+ * captura essa classe no embrulho — resolver pela classe do alvo reabria o
128
+ * ciclo da subclasse que sobrescreve e chama super). Não achando na própria
129
+ * classe, SOBE a hierarquia: cobre o original registrado num ancestral.
130
+ */
131
+ static func original(paraClasse classe: AnyClass?, seletor: Selector) -> IMP? {
132
+ trava.lock()
133
+ defer { trava.unlock() }
134
+ var atual: AnyClass? = classe
135
+ while let c = atual {
136
+ if let imp = originais[Chave(c, seletor)] { return imp }
137
+ atual = class_getSuperclass(c)
138
+ }
139
+ return nil
140
+ }
141
+ }
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "@pushmesh/sdk",
3
+ "version": "0.7.2",
4
+ "description": "SDK React Native do PUSHMESH — push com recibo real de entrega, reativação de permissão e banners in-app. API clássica inclusa.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "main": "./dist/cjs/index.js",
8
+ "types": "./dist/cjs/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/cjs/index.d.ts",
12
+ "import": "./dist/cjs/index.js",
13
+ "require": "./dist/cjs/index.js"
14
+ },
15
+ "./compat": {
16
+ "types": "./dist/cjs/compat.d.ts",
17
+ "import": "./dist/cjs/compat.js",
18
+ "require": "./dist/cjs/compat.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "bin": {
23
+ "pushmesh-doctor": "./dist/cjs/doctor.js"
24
+ },
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "android",
29
+ "ios",
30
+ "pushmesh-sdk.podspec",
31
+ "react-native.config.js",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "homepage": "https://pushmesh.io",
36
+ "bugs": {
37
+ "url": "https://pushmesh.io/suporte"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "scripts": {
46
+ "prebuild": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
47
+ "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.types.json && node scripts/minify.mjs",
48
+ "typecheck": "tsc --noEmit -p tsconfig.json",
49
+ "test": "jest",
50
+ "test:types": "tsd",
51
+ "size": "node scripts/check-size.mjs",
52
+ "version": "node scripts/sync-versao.mjs",
53
+ "prepack": "npm run build",
54
+ "prepublishOnly": "npm run build && npm test && npm run test:types && npm run size"
55
+ },
56
+ "peerDependencies": {
57
+ "@notifee/react-native": ">=7.0.0",
58
+ "@react-native-async-storage/async-storage": ">=1.17.0",
59
+ "react": ">=17.0.0",
60
+ "react-native": ">=0.71.0"
61
+ },
62
+ "devDependencies": {
63
+ "@notifee/react-native": "^9.1.8",
64
+ "@react-native-async-storage/async-storage": "^2.1.2",
65
+ "@types/jest": "^29.5.14",
66
+ "@types/node": "^22.10.0",
67
+ "@types/react": "^19.0.0",
68
+ "@types/react-test-renderer": "^19.0.0",
69
+ "jest": "^29.7.0",
70
+ "react": "19.2.0",
71
+ "react-native": "0.83.1",
72
+ "react-test-renderer": "19.2.0",
73
+ "terser": "^5.49.0",
74
+ "ts-jest": "^29.2.5",
75
+ "tsd": "^0.31.2",
76
+ "typescript": "^5.7.0"
77
+ },
78
+ "tsd": {
79
+ "directory": "__tests__/types"
80
+ },
81
+ "jest": {
82
+ "preset": "ts-jest",
83
+ "testEnvironment": "node",
84
+ "roots": [
85
+ "<rootDir>/__tests__"
86
+ ],
87
+ "testMatch": [
88
+ "**/*.test.ts",
89
+ "**/*.test.tsx"
90
+ ],
91
+ "moduleNameMapper": {
92
+ "^react-native$": "<rootDir>/__mocks__/react-native.ts",
93
+ "^@notifee/react-native$": "<rootDir>/__mocks__/notifee.ts",
94
+ "^@react-native-async-storage/async-storage$": "<rootDir>/__mocks__/async-storage.ts"
95
+ }
96
+ },
97
+ "keywords": [
98
+ "pushmesh",
99
+ "react-native",
100
+ "push-notifications",
101
+ "push",
102
+ "notifications",
103
+ "fcm",
104
+ "apns",
105
+ "firebase-cloud-messaging",
106
+ "in-app-messaging",
107
+ "in-app-message",
108
+ "delivery-receipt",
109
+ "android",
110
+ "ios",
111
+ "sdk"
112
+ ]
113
+ }
@@ -0,0 +1,32 @@
1
+ require 'json'
2
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
3
+
4
+ # Podspec do SDK PUSHMESH — modo zero-config iOS: APNs DIRETO.
5
+ # NENHUMA dependency de Firebase (essa é a feature): o servidor envia push
6
+ # via APNs HTTP/2 com a .p8 cadastrada no painel; o app só precisa da
7
+ # capability Push Notifications.
8
+ Pod::Spec.new do |s|
9
+ s.name = "pushmesh-sdk"
10
+ s.version = package["version"]
11
+ s.summary = "SDK React Native do PUSHMESH — push com recibo real de entrega, zero-config (Android FCM por código, iOS APNs direto)."
12
+ s.homepage = "https://pushmesh.io"
13
+ s.license = package["license"]
14
+ s.authors = "PushMesh"
15
+ s.platforms = { :ios => min_ios_version_supported } # alinha com o mínimo exigido pelo RN do host (15.1 no RN 0.83)
16
+ # O autolinking do RN instala este pod por CAMINHO (react-native.config.js →
17
+ # podspecPath), a partir do que o npm já baixou em node_modules — o `source`
18
+ # nunca é buscado nessa rota. Ele aponta para o tarball do npm (a distribuição
19
+ # oficial) só para o podspec ser válido fora do autolinking.
20
+ s.source = {
21
+ :http => "https://registry.npmjs.org/#{package['name']}/-/sdk-#{s.version}.tgz",
22
+ }
23
+ s.source_files = "ios/**/*.{swift,h,m,mm}"
24
+ # A Notification Service Extension NÃO entra no pod do app: ela é um ALVO
25
+ # SEPARADO do Xcode (outro processo, outro binário). Sem esta exclusão o
26
+ # glob acima a compilaria dentro do app — código morto no app, e a extensão
27
+ # continuaria sem existir onde precisa. Instalação: ios/NotificationService/LEIA-ME.md
28
+ s.exclude_files = "ios/NotificationService/**/*"
29
+ s.swift_version = "5.0"
30
+
31
+ s.dependency "React-Core"
32
+ end
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Autolinking dos módulos nativos do modo zero-config:
3
+ * Android (Firebase por código) e iOS (APNs direto, sem Firebase).
4
+ */
5
+ module.exports = {
6
+ dependency: {
7
+ platforms: {
8
+ android: {
9
+ sourceDir: './android',
10
+ packageImportPath: 'import io.pushmesh.sdk.PushMeshFirebasePackage;',
11
+ },
12
+ ios: {
13
+ podspecPath: './pushmesh-sdk.podspec',
14
+ },
15
+ },
16
+ },
17
+ };