@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.
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/android/build.gradle +82 -0
- package/android/src/main/AndroidManifest.xml +69 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshClickActivity.kt +124 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshFirebaseModule.kt +512 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshFirebasePackage.kt +20 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshMessageBuffer.kt +53 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshMessageRouter.kt +477 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshMessagingReceiver.kt +77 -0
- package/android/src/main/java/io/pushmesh/sdk/PushMeshMessagingService.kt +88 -0
- package/dist/cjs/boot.d.ts +7 -0
- package/dist/cjs/boot.js +1 -0
- package/dist/cjs/compat.d.ts +63 -0
- package/dist/cjs/compat.js +1 -0
- package/dist/cjs/doctor.d.ts +27 -0
- package/dist/cjs/doctor.js +2 -0
- package/dist/cjs/events.d.ts +21 -0
- package/dist/cjs/events.js +1 -0
- package/dist/cjs/http.d.ts +20 -0
- package/dist/cjs/http.js +1 -0
- package/dist/cjs/inapp/SkeletonShimmer.d.ts +1 -0
- package/dist/cjs/inapp/SkeletonShimmer.js +1 -0
- package/dist/cjs/inapp/auto.d.ts +1 -0
- package/dist/cjs/inapp/auto.js +1 -0
- package/dist/cjs/inapp/components.d.ts +14 -0
- package/dist/cjs/inapp/components.js +1 -0
- package/dist/cjs/inapp/engine.d.ts +1 -0
- package/dist/cjs/inapp/engine.js +1 -0
- package/dist/cjs/inapp/imageCache.d.ts +1 -0
- package/dist/cjs/inapp/imageCache.js +1 -0
- package/dist/cjs/inapp/index.d.ts +20 -0
- package/dist/cjs/inapp/index.js +1 -0
- package/dist/cjs/inapp/lifecycle.d.ts +1 -0
- package/dist/cjs/inapp/lifecycle.js +1 -0
- package/dist/cjs/inapp/normalize.d.ts +1 -0
- package/dist/cjs/inapp/normalize.js +1 -0
- package/dist/cjs/inapp/registro.d.ts +1 -0
- package/dist/cjs/inapp/registro.js +1 -0
- package/dist/cjs/inapp/sessao.d.ts +1 -0
- package/dist/cjs/inapp/sessao.js +1 -0
- package/dist/cjs/index.d.ts +118 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/perfil.d.ts +52 -0
- package/dist/cjs/perfil.js +1 -0
- package/dist/cjs/permissions.d.ts +54 -0
- package/dist/cjs/permissions.js +1 -0
- package/dist/cjs/players.d.ts +20 -0
- package/dist/cjs/players.js +1 -0
- package/dist/cjs/queue.d.ts +1 -0
- package/dist/cjs/queue.js +1 -0
- package/dist/cjs/receipts.d.ts +32 -0
- package/dist/cjs/receipts.js +1 -0
- package/dist/cjs/state.d.ts +21 -0
- package/dist/cjs/state.js +1 -0
- package/dist/cjs/storage.d.ts +1 -0
- package/dist/cjs/storage.js +1 -0
- package/dist/cjs/tags.d.ts +13 -0
- package/dist/cjs/tags.js +1 -0
- package/dist/cjs/triggers.d.ts +22 -0
- package/dist/cjs/triggers.js +1 -0
- package/dist/cjs/types.d.ts +258 -0
- package/dist/cjs/types.js +1 -0
- package/dist/cjs/zeroconfig.d.ts +45 -0
- package/dist/cjs/zeroconfig.js +1 -0
- package/ios/NotificationService/LEIA-ME.md +105 -0
- package/ios/NotificationService/PushMeshNotificationService.swift +384 -0
- package/ios/PushMeshApns.swift +746 -0
- package/ios/PushMeshApnsModule.m +76 -0
- package/ios/PushMeshSwizzleRegistro.swift +141 -0
- package/package.json +113 -0
- package/pushmesh-sdk.podspec +32 -0
- package/react-native.config.js +17 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AuthorizationStatus } from '@notifee/react-native';
|
|
2
|
+
import type { RpNotificationEventType, RpNotificationListener } from './events';
|
|
3
|
+
import { login as coreLogin, logout as coreLogout } from './players';
|
|
4
|
+
import { addTag, addTags, removeTag, removeTags } from './tags';
|
|
5
|
+
import type { PushMeshConfig } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* API CLÁSSICA do PUSHMESH — superfície compatível com SDKs de push do
|
|
8
|
+
* mercado, para o app host integrar sem reescrever suas chamadas:
|
|
9
|
+
*
|
|
10
|
+
* import { ClassicApi } from '@pushmesh/sdk/compat';
|
|
11
|
+
*
|
|
12
|
+
* Cobre: initialize, login/logout, Notifications.requestPermission/
|
|
13
|
+
* addEventListener, User tags e InAppMessages triggers.
|
|
14
|
+
*
|
|
15
|
+
* SÓ O APP ID BASTA: `ClassicApi.initialize(appId)` aponta para o servidor
|
|
16
|
+
* oficial. Para servidor próprio (self-host) ou local no desenvolvimento, há
|
|
17
|
+
* duas formas de apontar para outro endereço:
|
|
18
|
+
* ClassicApi.initialize(appId, { baseUrl: 'https://push.exemplo.com' })
|
|
19
|
+
* ou ClassicApi.setBaseUrl(url) ANTES do initialize (call site com 1 arg).
|
|
20
|
+
* options.baseUrl vence o setBaseUrl quando os dois existem.
|
|
21
|
+
*/
|
|
22
|
+
export interface ClassicInitOptions extends Partial<Omit<PushMeshConfig, 'appId' | 'baseUrl'>> {
|
|
23
|
+
/** Base URL do PUSHMESH. Opcional — sem ela vale o servidor oficial. */
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Define a baseUrl antes do initialize. */
|
|
27
|
+
declare function setBaseUrl(url: string): void;
|
|
28
|
+
declare function initialize(appId: string, options?: ClassicInitOptions): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Pedido de permissão com o priming da spec (SPEC_INAPP_REATIVACAO §4):
|
|
31
|
+
* se ainda dá para pedir o prompt NATIVO, pede; senão, com
|
|
32
|
+
* fallbackToSettings=true, abre a tela de notificações nos Ajustes.
|
|
33
|
+
* Retorna true se a permissão está concedida ao final.
|
|
34
|
+
*/
|
|
35
|
+
declare function requestPermission(fallbackToSettings?: boolean): Promise<boolean>;
|
|
36
|
+
declare function addEventListener(event: RpNotificationEventType, listener: RpNotificationListener): void;
|
|
37
|
+
declare function removeEventListener(event: RpNotificationEventType, listener: RpNotificationListener): void;
|
|
38
|
+
export declare const ClassicApi: {
|
|
39
|
+
initialize: typeof initialize;
|
|
40
|
+
setBaseUrl: typeof setBaseUrl;
|
|
41
|
+
login: typeof coreLogin;
|
|
42
|
+
logout: typeof coreLogout;
|
|
43
|
+
Notifications: {
|
|
44
|
+
requestPermission: typeof requestPermission;
|
|
45
|
+
addEventListener: typeof addEventListener;
|
|
46
|
+
removeEventListener: typeof removeEventListener;
|
|
47
|
+
};
|
|
48
|
+
User: {
|
|
49
|
+
addTag: typeof addTag;
|
|
50
|
+
addTags: typeof addTags;
|
|
51
|
+
removeTag: typeof removeTag;
|
|
52
|
+
removeTags: typeof removeTags;
|
|
53
|
+
};
|
|
54
|
+
InAppMessages: {
|
|
55
|
+
addTrigger: typeof import("./triggers").definirGatilho;
|
|
56
|
+
addTriggers: typeof import("./triggers").definirGatilhos;
|
|
57
|
+
removeTrigger: typeof import("./triggers").removerGatilho;
|
|
58
|
+
removeTriggers: typeof import("./triggers").removerGatilhos;
|
|
59
|
+
clearTriggers: typeof import("./triggers").limparGatilhos;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export default ClassicApi;
|
|
63
|
+
export { AuthorizationStatus };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?function(o,m,k,k2){void 0===k2&&(k2=k);var desc=Object.getOwnPropertyDescriptor(m,k);desc&&!("get"in desc?!m.__esModule:desc.writable||desc.configurable)||(desc={enumerable:true,get:function(){return m[k]}}),Object.defineProperty(o,k2,desc)}:function(o,m,k,k2){void 0===k2&&(k2=k),o[k2]=m[k]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(o,v){Object.defineProperty(o,"default",{enumerable:true,value:v})}:function(o,v){o.default=v}),__importStar=this&&this.__importStar||function(){var ownKeys=function(o){return ownKeys=Object.getOwnPropertyNames||function(o){var ar=[];for(var k in o)Object.prototype.hasOwnProperty.call(o,k)&&(ar[ar.length]=k);return ar},ownKeys(o)};return function(mod){if(mod&&mod.__esModule)return mod;var result={};if(null!=mod)for(var k=ownKeys(mod),i=0;i<k.length;i++)"default"!==k[i]&&__createBinding(result,mod,k[i]);return __setModuleDefault(result,mod),result}}();Object.defineProperty(exports,"__esModule",{value:true}),exports.AuthorizationStatus=exports.ClassicApi=void 0;const react_native_1=__importStar(require("@notifee/react-native"));Object.defineProperty(exports,"AuthorizationStatus",{enumerable:true,get:function(){return react_native_1.AuthorizationStatus}});const boot_1=require("./boot"),events_1=require("./events"),permissions_1=require("./permissions"),players_1=require("./players"),tags_1=require("./tags"),triggers_1=require("./triggers");let baseUrlFromSetter=null;function setBaseUrl(url){baseUrlFromSetter=url}async function initialize(appId,options){const baseUrl=options?.baseUrl??baseUrlFromSetter??void 0,{baseUrl:_ignored,...rest}=options??{};await(0,boot_1.boot)({...rest,appId:appId,baseUrl:baseUrl})}async function requestPermission(fallbackToSettings){return false!==fallbackToSettings?(0,permissions_1.reativarNotificacoes)():(await(0,permissions_1.canRequestNatively)()&&await react_native_1.default.requestPermission(),1===await(0,permissions_1.checkAndReport)())}function addEventListener(event,listener){(0,events_1.addNotificationListener)(event,listener)}function removeEventListener(event,listener){(0,events_1.removeNotificationListener)(event,listener)}exports.ClassicApi={initialize:initialize,setBaseUrl:setBaseUrl,login:players_1.login,logout:players_1.logout,Notifications:{requestPermission:requestPermission,addEventListener:addEventListener,removeEventListener:removeEventListener},User:{addTag:tags_1.addTag,addTags:tags_1.addTags,removeTag:tags_1.removeTag,removeTags:tags_1.removeTags},InAppMessages:{addTrigger:triggers_1.addTrigger,addTriggers:triggers_1.addTriggers,removeTrigger:triggers_1.removeTrigger,removeTriggers:triggers_1.removeTriggers,clearTriggers:triggers_1.clearTriggers}},exports.default=exports.ClassicApi;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pushmesh-doctor — diagnóstico da integração PUSHMESH.
|
|
4
|
+
*
|
|
5
|
+
* Roda em Node (não no device): valida o lado do servidor e, se receber a
|
|
6
|
+
* api_key do app, o estado do player. Cada falha sai com "como corrigir"
|
|
7
|
+
* (a lei do erro que ensina).
|
|
8
|
+
*
|
|
9
|
+
* Uso:
|
|
10
|
+
* npx pushmesh-doctor --base-url https://push.pushmesh.io \
|
|
11
|
+
* --app-id <uuid> [--player-id <uuid>] [--api-key pm_live_...] [--timeout 8000]
|
|
12
|
+
*
|
|
13
|
+
* Exit code: 0 = tudo verde · 1 = ao menos uma falha.
|
|
14
|
+
*/
|
|
15
|
+
interface Check {
|
|
16
|
+
nome: string;
|
|
17
|
+
ok: boolean | null;
|
|
18
|
+
detalhe: string;
|
|
19
|
+
comoCorrigir?: string;
|
|
20
|
+
}
|
|
21
|
+
declare function parseArgs(argv: string[]): Record<string, string>;
|
|
22
|
+
declare function fetchJson(url: string, timeoutMs: number, headers?: Record<string, string>): Promise<{
|
|
23
|
+
status: number;
|
|
24
|
+
json: any;
|
|
25
|
+
}>;
|
|
26
|
+
declare const USO = "pushmesh-doctor \u2014 diagn\u00F3stico da integra\u00E7\u00E3o PushMesh.\n\nUso:\n npx pushmesh-doctor --base-url <url> --app-id <uuid> [op\u00E7\u00F5es]\n\nObrigat\u00F3rios:\n --base-url <url> servidor PushMesh (oficial: https://push.pushmesh.io)\n --app-id <uuid> App ID do app no painel\n\nOpcionais:\n --player-id <uuid> confere um aparelho j\u00E1 registrado\n --api-key pm_live_\u2026 destrava as checagens que exigem autentica\u00E7\u00E3o\n --timeout <ms> timeout por requisi\u00E7\u00E3o (padr\u00E3o: 8000)\n -h, --help mostra esta ajuda\n\nExit code: 0 = tudo verde \u00B7 1 = ao menos uma falha.\nDoc: https://docs.pushmesh.io";
|
|
27
|
+
declare function main(): Promise<void>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";function parseArgs(argv){const args={};for(let i=0;i<argv.length;i++){const a=argv[i];if(a.startsWith("--")){const key=a.slice(2),next=argv[i+1];next&&!next.startsWith("--")?(args[key]=next,i++):args[key]="true"}}return args}async function fetchJson(url,timeoutMs,headers){const controller=new AbortController,timer=setTimeout(()=>controller.abort(),timeoutMs);try{const res=await fetch(url,{headers:headers,signal:controller.signal}),text=await res.text();let json=null;try{json=JSON.parse(text)}catch{json=text}return{status:res.status,json:json}}finally{clearTimeout(timer)}}const USO="pushmesh-doctor — diagnóstico da integração PushMesh.\n\nUso:\n npx pushmesh-doctor --base-url <url> --app-id <uuid> [opções]\n\nObrigatórios:\n --base-url <url> servidor PushMesh (oficial: https://push.pushmesh.io)\n --app-id <uuid> App ID do app no painel\n\nOpcionais:\n --player-id <uuid> confere um aparelho já registrado\n --api-key pm_live_… destrava as checagens que exigem autenticação\n --timeout <ms> timeout por requisição (padrão: 8000)\n -h, --help mostra esta ajuda\n\nExit code: 0 = tudo verde · 1 = ao menos uma falha.\nDoc: https://docs.pushmesh.io";async function main(){const argv=process.argv.slice(2);(0===argv.length||argv.includes("--help")||argv.includes("-h"))&&(console.log(USO),process.exit(0));const args=parseArgs(argv),checks=[],baseUrl=(args["base-url"]||"").replace(/\/+$/,"");baseUrl||(console.error("FALHA: --base-url é obrigatório.\n Como corrigir: npx pushmesh-doctor --base-url https://push.pushmesh.io --app-id <uuid>"),process.exit(1));const timeoutMs=Number(args.timeout||8e3);let health=null;try{const res=await fetchJson(`${baseUrl}/health`,timeoutMs);health=res.json,200===res.status&&health&&"ok"===health.status?checks.push({nome:"baseUrl alcançável + /health ok",ok:true,detalhe:`versao=${health.versao??"?"} papel=${health.papel??"?"} pg=${health.pg??"?"} valkey=${health.valkey??"?"}`}):checks.push({nome:"baseUrl alcançável + /health ok",ok:false,detalhe:`HTTP ${res.status}`,comoCorrigir:'o /health do PUSHMESH deve responder 200 {"status":"ok"}. Verifique se o serviço está no ar (systemctl status pushmesh) e se o LB está healthy.'})}catch(e){checks.push({nome:"baseUrl alcançável + /health ok",ok:false,detalhe:String(e),comoCorrigir:"confira a URL (sem barra final), DNS/TLS do domínio e se esta máquina alcança o servidor (VPN/firewall). "})}health&&"ok"===health.status&&("ok"!==health.pg&&checks.push({nome:"Postgres saudável",ok:false,detalhe:`pg=${health.pg} pg_down_ha_s=${health.pg_down_ha_s??"?"}`,comoCorrigir:"O banco do serviço está fora do ar e a API responde 503 por contrato — não é a integração do seu app. Aguarde o restabelecimento ou fale com o suporte."}),"ok"!==health.valkey&&checks.push({nome:"Valkey saudável",ok:null,detalhe:`valkey=${health.valkey} — o serviço está em modo degradado, mas segue entregando`}));const appId=args["app-id"],playerId=args["player-id"],apiKey=args["api-key"];if(playerId&&!apiKey)checks.push({nome:"player registrado",ok:null,detalhe:"pulado: GET /players/:id exige --api-key (rota de servidor, Basic auth)"});else if(playerId&&apiKey){appId||(console.error("FALHA: --app-id é obrigatório junto com --player-id."),process.exit(1));try{const res=await fetchJson(`${baseUrl}/api/v1/players/${encodeURIComponent(playerId)}?app_id=${encodeURIComponent(appId)}`,timeoutMs,{Authorization:`Basic ${apiKey}`});if(200===res.status&&res.json){const p=res.json;checks.push({nome:"player registrado",ok:true,detalhe:`id=${playerId}`}),checks.push(p.identifier?{nome:"token de push presente",ok:true,detalhe:"identifier preenchido"}:{nome:"token de push presente",ok:false,detalhe:"identifier vazio",comoCorrigir:"o device se registrou sem token. No app, confira config.getToken / PushMesh.setToken e o google-services.json."});const nt=p.notification_types;checks.push(1===nt?{nome:"permissão de notificação",ok:true,detalhe:"notification_types=1 (ativo)"}:{nome:"permissão de notificação",ok:0===nt&&null,detalhe:`notification_types=${nt}`,comoCorrigir:-2===nt?"usuário bloqueou. Use a campanha Reativa: botão open_notification_settings + detecção de virada no foreground.":"permissão nunca solicitada. canRequestNatively()=true: dá para pedir o prompt nativo (spec §4)."}),p.invalid_identifier&&checks.push({nome:"token válido no FCM/APNs",ok:false,detalhe:"invalid_identifier=true (token morto — UNREGISTERED)",comoCorrigir:"o app precisa abrir uma vez para re-registrar com token novo (upsert por app_id+token)."})}else 404===res.status?checks.push({nome:"player registrado",ok:false,detalhe:`404 — player ${playerId} não existe nesse tenant`,comoCorrigir:"confira app_id e player_id (o player_id é o devices.uuid devolvido pelo POST /players, guardado no AsyncStorage pm:player_id)."}):401===res.status?checks.push({nome:"player registrado",ok:false,detalhe:"401 — api_key ausente/revogada ou app_id ≠ app da chave",comoCorrigir:"confira a api_key do app; se rotacionou, a antiga segue válida por 24h."}):checks.push({nome:"player registrado",ok:false,detalhe:`HTTP ${res.status}: ${JSON.stringify(res.json)}`})}catch(e){checks.push({nome:"player registrado",ok:false,detalhe:String(e),comoCorrigir:"falha de rede consultando o player — veja o item baseUrl acima."})}}console.log(`\npushmesh-doctor — ${baseUrl}\n${"=".repeat(60)}`);let falhas=0;for(const c of checks){const marca=true===c.ok?"OK ":null===c.ok?"INFO ":"FALHA";false===c.ok&&falhas++,console.log(`${marca} ${c.nome}: ${c.detalhe}`),c.comoCorrigir&&console.log(` → como corrigir: ${c.comoCorrigir}`)}console.log("=".repeat(60)),console.log(0===falhas?"Tudo verde.":`${falhas} falha(s) — corrija na ordem acima.`),process.exit(0===falhas?0:1)}main().catch(e=>{console.error(`pushmesh-doctor quebrou: ${String(e)}`),process.exit(1)});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emitter interno de eventos de notificação (base da API clássica e
|
|
3
|
+
* do PushMesh.notifications). Sem dependência — Set de listeners por tipo.
|
|
4
|
+
*
|
|
5
|
+
* - 'foregroundWillDisplay': push recebido com o app em foreground
|
|
6
|
+
* (disparado por PushMesh.receipts.handlePushData, com dedup LRU-64 —
|
|
7
|
+
* duplicata at-least-once do FCM não dispara o listener 2x).
|
|
8
|
+
* - 'click': usuário tocou na notificação (o app host chama
|
|
9
|
+
* PushMesh.notifications.handleClick no getInitialNotification/onNotificationOpenedApp).
|
|
10
|
+
*/
|
|
11
|
+
export type RpNotificationEventType = 'click' | 'foregroundWillDisplay';
|
|
12
|
+
export interface RpNotificationEvent {
|
|
13
|
+
/** `data` cru do push (rota, pm_msg_id, etc.). */
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export type RpNotificationListener = (event: RpNotificationEvent) => void;
|
|
17
|
+
export declare function addNotificationListener(type: RpNotificationEventType, listener: RpNotificationListener): void;
|
|
18
|
+
export declare function removeNotificationListener(type: RpNotificationEventType, listener: RpNotificationListener): void;
|
|
19
|
+
export declare function emitNotificationEvent(type: RpNotificationEventType, data: Record<string, unknown>): void;
|
|
20
|
+
/** Apenas para testes. */
|
|
21
|
+
export declare function __resetListenersForTests(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.addNotificationListener=addNotificationListener,exports.removeNotificationListener=removeNotificationListener,exports.emitNotificationEvent=emitNotificationEvent,exports.__resetListenersForTests=__resetListenersForTests;const listeners={click:new Set,foregroundWillDisplay:new Set};function addNotificationListener(type,listener){listeners[type].add(listener)}function removeNotificationListener(type,listener){listeners[type].delete(listener)}function emitNotificationEvent(type,data){for(const listener of listeners[type])try{listener({data:data})}catch(e){console.warn(`[PushMesh] listener de '${type}' quebrou`,e)}}function __resetListenersForTests(){listeners.click.clear(),listeners.foregroundWillDisplay.clear()}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP mínimo sobre fetch nativo (NADA de axios — restrição do projeto).
|
|
3
|
+
* Todas as rotas de SDK são públicas: app_id vai no corpo, sem Authorization
|
|
4
|
+
* (rate limit por IP no servidor).
|
|
5
|
+
*/
|
|
6
|
+
export interface HttpResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
status: number;
|
|
9
|
+
json: unknown;
|
|
10
|
+
/** Header Retry-After (429/503), em ms — a fila offline honra este valor. */
|
|
11
|
+
retryAfterMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/** Erro de rede/timeout — o chamador decide se enfileira para retry offline. */
|
|
14
|
+
export declare class NetworkError extends Error {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare function request(method: 'GET' | 'POST' | 'PUT', path: string, body?: Record<string, unknown>, extraHeaders?: Record<string, string>): Promise<HttpResult>;
|
|
18
|
+
export declare function post(path: string, body: Record<string, unknown>): Promise<HttpResult>;
|
|
19
|
+
export declare function put(path: string, body: Record<string, unknown>): Promise<HttpResult>;
|
|
20
|
+
export declare function get(path: string, headers?: Record<string, string>): Promise<HttpResult>;
|
package/dist/cjs/http.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.NetworkError=void 0,exports.request=request,exports.post=post,exports.put=put,exports.get=get;const state_1=require("./state");class NetworkError extends Error{constructor(message){super(message),this.name="NetworkError"}}async function parseBody(res){const text=await res.text();if(!text)return null;try{return JSON.parse(text)}catch{return text}}exports.NetworkError=NetworkError;const TETO_RETRY_AFTER_MS=18e5;function parseRetryAfter(headers){const raw=headers?.get?.("Retry-After");if(!raw)return;const segundos=Number(raw),ms=Number.isFinite(segundos)?1e3*segundos:Date.parse(raw)-Date.now();return Number.isNaN(ms)?void 0:Math.min(Math.max(0,ms),18e5)}async function request(method,path,body,extraHeaders){const timeoutMs=(0,state_1.requireConfig)().requestTimeoutMs??1e4,controller=new AbortController,timer=setTimeout(()=>controller.abort(),timeoutMs);try{const headers={...extraHeaders};body&&(headers["Content-Type"]="application/json");const res=await fetch(`${(0,state_1.baseUrl)()}${path}`,{method:method,headers:Object.keys(headers).length?headers:void 0,body:body?JSON.stringify(body):void 0,signal:controller.signal});return{ok:res.ok,status:res.status,json:await parseBody(res),retryAfterMs:parseRetryAfter(res.headers)}}catch(e){throw new NetworkError(`[PushMesh] falha de rede em ${method} ${path}: ${String(e)}`)}finally{clearTimeout(timer)}}function post(path,body){return request("POST",path,body)}function put(path,body){return request("PUT",path,body)}function get(path,headers){return request("GET",path,void 0,headers)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.SkeletonShimmer=SkeletonShimmer;const jsx_runtime_1=require("react/jsx-runtime"),react_1=require("react"),react_native_1=require("react-native");function SkeletonShimmer({style:style}){const opacity=(0,react_1.useRef)(new react_native_1.Animated.Value(.35)).current;return(0,react_1.useEffect)(()=>{const loop=react_native_1.Animated.loop(react_native_1.Animated.sequence([react_native_1.Animated.timing(opacity,{toValue:.8,duration:700,useNativeDriver:true}),react_native_1.Animated.timing(opacity,{toValue:.35,duration:700,useNativeDriver:true})]));return loop.start(),()=>loop.stop()},[opacity]),(0,jsx_runtime_1.jsx)(react_native_1.Animated.View,{style:[styles.base,style,{opacity:opacity}]})}const styles=react_native_1.StyleSheet.create({base:{backgroundColor:"#E1E5EA",borderRadius:8}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.ATRASO_CHECAGEM_MS=void 0,exports.observarWrapperDoApp=observarWrapperDoApp,exports.montarInAppAutomaticamente=montarInAppAutomaticamente,exports.__resetAutoForTests=__resetAutoForTests;const jsx_runtime_1=require("react/jsx-runtime"),react_native_1=require("react-native"),state_1=require("../state"),components_1=require("./components"),registro_1=require("./registro");function registroDeApp(){try{return react_native_1.AppRegistry??null}catch{return null}}exports.ATRASO_CHECAGEM_MS=5e3;let provedorDoApp=null,setterOriginal=null,nossoSetter=null,instalado=false,timerChecagem=null;function comporWrapper(Anterior){return function(props){const{children:children,...resto}=props;return(0,jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment,{children:[Anterior?(0,jsx_runtime_1.jsx)(Anterior,{...resto,children:children}):children,(0,jsx_runtime_1.jsx)(components_1.HostInApp,{auto:true})]})}}const provedorComposto=parametros=>comporWrapper(provedorDoApp?provedorDoApp(parametros):null);function observarWrapperDoApp(){if(!nossoSetter)try{const registro=registroDeApp(),original=registro?.setWrapperComponentProvider;if("function"!=typeof original||!registro)return;if(original.__pushmesh)return;const meu=provedor=>{provedorDoApp="function"==typeof provedor?provedor:null,original(instalado?provedorComposto:provedor)};if(meu.__pushmesh=true,registro.setWrapperComponentProvider=meu,registro.setWrapperComponentProvider!==meu)return;setterOriginal=original,nossoSetter=meu}catch{}}function montarInAppAutomaticamente(){if(!instalado&&(observarWrapperDoApp(),setterOriginal)){try{instalado=true,setterOriginal(provedorComposto)}catch{return void(instalado=false)}agendarChecagem()}}function agendarChecagem(){(0,state_1.emDesenvolvimento)()&&!timerChecagem&&(timerChecagem=setTimeout(()=>{timerChecagem=null,(0,registro_1.algumHostMontou)()||console.warn("[PushMesh] o host in-app não montou. Causa provável: o PushMesh.init() rodou DEPOIS de a raiz do app aparecer (dentro de componente/useEffect), e o wrapper automático do React Native só vale para o próximo render da raiz. Como corrigir: chame o init no escopo do módulo (index.js/App.tsx, fora de componente) — ou monte <PushMeshInAppHost /> uma vez na raiz do app.")},exports.ATRASO_CHECAGEM_MS))}function __resetAutoForTests(){try{const registro=registroDeApp();registro&&nossoSetter&&setterOriginal&®istro.setWrapperComponentProvider===nossoSetter&&(registro.setWrapperComponentProvider=setterOriginal)}catch{}provedorDoApp=null,setterOriginal=null,nossoSetter=null,instalado=false,timerChecagem&&clearTimeout(timerChecagem),timerChecagem=null,observarWrapperDoApp()}observarWrapperDoApp();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { RpInAppMessage } from '../types';
|
|
3
|
+
export interface PushMeshInAppHostProps {
|
|
4
|
+
/** Chamado após exibir uma mensagem (o app host encadeia lógica própria). */
|
|
5
|
+
onDisplayed?: (message: RpInAppMessage) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Host das mensagens in-app — a montagem À MÃO (opcional desde a montagem
|
|
9
|
+
* automática do init). Monte UMA vez na raiz; se o automático também estiver
|
|
10
|
+
* ligado, só um dos dois desenha e o SDK avisa em desenvolvimento.
|
|
11
|
+
*/
|
|
12
|
+
export declare function PushMeshInAppHost({ onDisplayed }: PushMeshInAppHostProps): React.JSX.Element | null;
|
|
13
|
+
/** Alias amigável — mesmo componente (a API "só o App ID basta"). */
|
|
14
|
+
export declare const PushMeshInApp: typeof PushMeshInAppHost;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.PushMeshInApp=void 0,exports.HostInApp=HostInApp,exports.PushMeshInAppHost=PushMeshInAppHost;const jsx_runtime_1=require("react/jsx-runtime"),react_1=require("react"),react_native_1=require("react-native"),engine_1=require("./engine"),imageCache_1=require("./imageCache"),index_1=require("./index"),lifecycle_1=require("./lifecycle"),normalize_1=require("./normalize"),registro_1=require("./registro"),SkeletonShimmer_1=require("./SkeletonShimmer"),FUNDO_PADRAO="#ffffff",TEXTO_PADRAO="#111111",BOTAO_FUNDO_PADRAO="#1565c0",BOTAO_TEXTO_PADRAO="#ffffff",RAIO_PADRAO=12,PADDING_PADRAO=16,ESCURECER_PADRAO=.5,BOTAO_PESO_PADRAO="600",BOTAO_TAMANHO_PADRAO=15,CLOSE_CLARO="#ffffff",CLOSE_ESCURO="#111111",LIMIAR_FUNDO=.179,LIMIAR_GLIFO=.5,CHIP_ESCURO="rgba(0,0,0,0.5)",CHIP_CLARO="rgba(255,255,255,0.7)",HALO_ESCURO="rgba(0,0,0,0.75)",HALO_CLARO="rgba(255,255,255,0.85)",CLOSE_RESPIRO=6;function haloDe(cor){return(luminancia(cor)??0)>=.5?HALO_ESCURO:HALO_CLARO}function pesoRN(peso){return"negrito"===peso?"700":"medio"===peso?"500":"400"}function fonteRN(fonte){return"serifada"===fonte?"ios"===react_native_1.Platform.OS?"Georgia":"serif":"monoespacada"===fonte?"ios"===react_native_1.Platform.OS?"Menlo":"monospace":void 0}function alinhamentoRN(a){return"centro"===a?"center":"direita"===a?"right":"left"}function glifoClose(icone){return"seta"===icone?"⌄":"✕"}function corValida(v){return"string"==typeof v&&""!==v.trim()?v.trim():null}function rgbDaCor(cor){if(!cor||"#"!==cor.charAt(0))return null;const h=cor.slice(1),curto=3===h.length||4===h.length;if(!curto&&6!==h.length&&8!==h.length)return null;if(!/^[0-9a-fA-F]+$/.test(h))return null;const canal=i=>parseInt(curto?h.charAt(i)+h.charAt(i):h.slice(2*i,2*i+2),16);return[canal(0),canal(1),canal(2)]}function luminancia(cor){const rgb=rgbDaCor(cor);if(!rgb)return null;const canal=c=>{const v=c/255;return v<=.03928?v/12.92:Math.pow((v+.055)/1.055,2.4)};return.2126*canal(rgb[0])+.7152*canal(rgb[1])+.0722*canal(rgb[2])}function arteAtrasDoClose(estilo,blocos){if(corValida(estilo.fundo_imagem))return true;const primeiro=blocos[0];return!!primeiro&&"imagem"===primeiro.tipo}function resolverClose(estilo,blocos){const close=estilo.close??{},arte=arteAtrasDoClose(estilo,blocos);let cor=corValida(close.cor);if(!cor)if(arte)cor="#ffffff";else{const l=luminancia(corValida(estilo.fundo));cor=null===l?corValida(estilo.texto)??"#111111":l<.179?"#ffffff":"#111111"}if(null===close.fundo)return{cor:cor,chip:null,halo:arte?haloDe(cor):null};const declarado=corValida(close.fundo);if(declarado)return{cor:cor,chip:declarado,halo:null};const glifoClaro=(luminancia(cor)??0)>=.5;return"x_circulo"===close.icone?{cor:cor,chip:glifoClaro?CHIP_ESCURO:CHIP_CLARO,halo:null}:{cor:cor,chip:null,halo:arte?haloDe(cor):null}}function ImagemBloco({bloco:bloco,estilo:estilo,padding:padding,primeiro:primeiro,ultimo:ultimo,onPress:onPress,testID:testID}){const[pronta,setPronta]=(0,react_1.useState)(null),[proporcao,setProporcao]=(0,react_1.useState)(null);(0,react_1.useEffect)(()=>{let vivo=true;return(0,imageCache_1.ensureImage)(bloco.url).then(ok=>{vivo&&(ok?react_native_1.Image.getSize(bloco.url,(w,h)=>{vivo&&(setProporcao(w>0&&h>0?w/h:2),setPronta(true))},()=>{vivo&&(setProporcao(2),setPronta(true))}):setPronta(false))}),()=>{vivo=false}},[bloco.url]);const sangra=true===bloco.sangra,m=bloco.margens??{},mTopo=m.topo??0,mDir=m.direita??0,mBaixo=m.baixo??0,mEsq=m.esquerda??0,raioCartao=estilo.raio??12,dims={alignSelf:"stretch",aspectRatio:proporcao??2,marginTop:(sangra&&primeiro?-padding:0)+mTopo,marginBottom:(sangra&&ultimo?-padding:8)+mBaixo,marginLeft:(sangra?-padding:0)+mEsq,marginRight:(sangra?-padding:0)+mDir};if(sangra?(dims.borderTopLeftRadius=primeiro?raioCartao:0,dims.borderTopRightRadius=primeiro?raioCartao:0,dims.borderBottomLeftRadius=ultimo?raioCartao:0,dims.borderBottomRightRadius=ultimo?raioCartao:0):dims.borderRadius=bloco.raio??0,null===pronta)return(0,jsx_runtime_1.jsx)(SkeletonShimmer_1.SkeletonShimmer,{style:dims});if(false===pronta)return null;const img=(0,jsx_runtime_1.jsx)(react_native_1.Image,{source:{uri:bloco.url},style:[dims,{overflow:"hidden"}],resizeMode:"cover",testID:testID});return onPress?(0,jsx_runtime_1.jsx)(react_native_1.TouchableOpacity,{activeOpacity:.85,onPress:onPress,testID:`${testID}-acao`,children:img}):img}function TextoBloco({bloco:bloco,estilo:estilo}){return(0,jsx_runtime_1.jsx)(react_native_1.Text,{style:{color:bloco.cor??estilo.texto??"#111111",fontSize:bloco.tamanho??16,fontWeight:pesoRN(bloco.peso),fontStyle:bloco.italico?"italic":"normal",textDecorationLine:bloco.sublinhado?"underline":"none",fontFamily:fonteRN(bloco.fonte),textAlign:alinhamentoRN(bloco.alinhamento),marginBottom:8},children:bloco.texto})}function BotaoBloco({bloco:bloco,estilo:estilo,onPress:onPress,testID:testID}){const alinhado=bloco.alinhamento?alinhamentoRN(bloco.alinhamento):null;return(0,jsx_runtime_1.jsx)(react_native_1.TouchableOpacity,{style:{backgroundColor:bloco.cor_fundo??estilo.botao_fundo??"#1565c0",borderRadius:bloco.raio??estilo.raio??8,paddingVertical:12,alignItems:alinhado?"stretch":"center",marginTop:8},onPress:onPress,testID:testID,children:(0,jsx_runtime_1.jsx)(react_native_1.Text,{style:{color:bloco.cor_texto??estilo.botao_texto??"#ffffff",fontSize:bloco.tamanho??15,fontWeight:bloco.peso?pesoRN(bloco.peso):"600",fontStyle:bloco.italico?"italic":"normal",textDecorationLine:bloco.sublinhado?"underline":"none",fontFamily:fonteRN(bloco.fonte),textAlign:alinhado??void 0},children:bloco.texto})})}function MessageView({message:message,blocos:blocos,estilo:estilo,oc:oc,onClose:onClose}){const reportar=(0,react_1.useCallback)((evento,botao)=>{(0,engine_1.enviarRecibo)(message.id,message.prova,evento,oc,botao).catch(()=>{})},[message.id,message.prova,oc]);(0,react_1.useEffect)(()=>{reportar("impressao");const ms=message.regras?.fechar_apos_ms??0;if(ms>0){const t=setTimeout(()=>onClose(),ms);return()=>clearTimeout(t)}},[]);const fechar=(0,react_1.useCallback)(comRecibo=>{comRecibo&&reportar("fechou"),onClose()},[reportar,onClose]),executarEFechar=(0,react_1.useCallback)((acao,botao)=>{reportar("clique",botao),(0,index_1.executeAction)(acao,message.id).catch(()=>{}),onClose()},[reportar,onClose,message.id]);let ordinalBotao=0;const filhos=blocos.map((b,i)=>{switch(b.tipo){case"texto":return(0,jsx_runtime_1.jsx)(TextoBloco,{bloco:b,estilo:estilo},i);case"imagem":{const img=b,acao=img.acao_click??null;return(0,jsx_runtime_1.jsx)(ImagemBloco,{bloco:img,estilo:estilo,padding:estilo.padding_px??16,primeiro:0===i,ultimo:i===blocos.length-1,onPress:acao?()=>executarEFechar(acao):void 0,testID:`rp-inapp-imagem-${i}`},i)}case"botao":{const bt=b,papel=0===ordinalBotao?"principal":"secundario";return ordinalBotao+=1,(0,jsx_runtime_1.jsx)(BotaoBloco,{bloco:bt,estilo:estilo,onPress:()=>executarEFechar(bt.acao,papel),testID:`rp-inapp-botao-${papel}`},i)}case"espacador":return(0,jsx_runtime_1.jsx)(react_native_1.View,{style:{height:b.altura}},i);default:return null}}),padding=estilo.padding_px??16,raio=estilo.raio??12,acaoCard=estilo.acao_card??null,close=resolverClose(estilo,blocos),ladoDoClose=(estilo.close?.tamanho??24)+12,card=(0,jsx_runtime_1.jsxs)(react_native_1.TouchableOpacity,{activeOpacity:acaoCard?.9:1,onPress:acaoCard?()=>executarEFechar(acaoCard):()=>{},style:[styles.card,{backgroundColor:estilo.fundo??"#ffffff",borderRadius:raio,padding:padding}],testID:"rp-inapp-card",children:["string"==typeof estilo.fundo_imagem?(0,jsx_runtime_1.jsx)(FundoImagem,{url:estilo.fundo_imagem,raio:raio}):null,filhos,false===estilo.close?.mostrar?null:(0,jsx_runtime_1.jsx)(react_native_1.TouchableOpacity,{style:[styles.close,close.chip?{backgroundColor:close.chip,width:ladoDoClose,height:ladoDoClose,borderRadius:ladoDoClose/2,padding:0,alignItems:"center",justifyContent:"center"}:null],onPress:()=>fechar(true),testID:"rp-inapp-close",accessibilityLabel:"Fechar",children:(0,jsx_runtime_1.jsx)(react_native_1.Text,{style:{color:close.cor,fontSize:estilo.close?.tamanho??24,fontWeight:"600",...close.halo?{textShadowColor:close.halo,textShadowOffset:{width:0,height:0},textShadowRadius:4}:null},children:glifoClose(estilo.close?.icone)})})]});if("modal"===message.formato)return(0,jsx_runtime_1.jsx)(react_native_1.Modal,{visible:true,transparent:true,animationType:"fade",onRequestClose:()=>fechar(true),children:(0,jsx_runtime_1.jsx)(react_native_1.TouchableOpacity,{activeOpacity:1,onPress:()=>fechar(true),style:[styles.veil,{backgroundColor:`rgba(0,0,0,${estilo.escurecer??.5})`}],testID:"rp-inapp-veil",children:card})});if("fullscreen"===message.formato)return(0,jsx_runtime_1.jsx)(react_native_1.Modal,{visible:true,animationType:"fade",onRequestClose:()=>fechar(true),children:(0,jsx_runtime_1.jsx)(react_native_1.View,{style:[styles.full,{backgroundColor:estilo.fundo??"#ffffff"}],children:card})});const posicao="banner_bottom"===message.formato?styles.bannerBottom:styles.bannerTop;return(0,jsx_runtime_1.jsx)(react_native_1.View,{style:[styles.banner,posicao],children:card})}function FundoImagem({url:url,raio:raio}){const[ok,setOk]=(0,react_1.useState)(null);(0,react_1.useEffect)(()=>{let vivo=true;return(0,imageCache_1.ensureImage)(url).then(r=>vivo&&setOk(r)),()=>{vivo=false}},[url]);const preencher={position:"absolute",top:0,left:0,right:0,bottom:0,borderRadius:raio};return null===ok?(0,jsx_runtime_1.jsx)(SkeletonShimmer_1.SkeletonShimmer,{style:preencher}):false===ok?null:(0,jsx_runtime_1.jsx)(react_native_1.Image,{source:{uri:url},resizeMode:"cover",style:preencher})}function HostInApp({auto:auto,onDisplayed:onDisplayed}){const dono=(0,registro_1.useDonoDoHost)(auto),[visivel,setVisivel]=(0,react_1.useState)(null),visivelRef=(0,react_1.useRef)(false),canceladoRef=(0,react_1.useRef)(false),reagendarRef=(0,react_1.useRef)(null);(0,react_1.useEffect)(()=>{if(!dono)return;let timer,timerSessao;canceladoRef.current=false;const agendarSessao=async()=>{timerSessao&&clearTimeout(timerSessao),timerSessao=void 0;const falta=await(0,engine_1.proximoDisparoSessaoMs)(Date.now());null===falta||canceladoRef.current||visivelRef.current||(timerSessao=setTimeout(()=>rodarSeguro("durante"),falta+50))},rodarSeguro=momento=>{(async momento=>{if(canceladoRef.current||visivelRef.current)return;const r=await(0,engine_1.avaliar)(momento);if(canceladoRef.current||visivelRef.current)return;if(!r)return void await agendarSessao();const{message:message}=r,{blocos:blocos,estilo:estilo}=(0,normalize_1.normalizar)(message),artes=[];for(const b of blocos)"imagem"===b.tipo&&"string"==typeof b.url&&artes.push((0,imageCache_1.ensureImage)(b.url).catch(()=>false));if("string"==typeof estilo.fundo_imagem&&artes.push((0,imageCache_1.ensureImage)(estilo.fundo_imagem).catch(()=>false)),!blocos.some(b=>"texto"===b.tipo&&""!==String(b.texto??"").trim()||"botao"===b.tipo)&&artes.length>0){const prontas=await Promise.all(artes);if(canceladoRef.current||visivelRef.current)return;if(!prontas.some(Boolean))return}const atraso=Math.max(0,Math.min(6e4,message.gatilho?.atraso_ms??0));timer=setTimeout(()=>{(async()=>{if(canceladoRef.current||visivelRef.current)return;const oc=await(0,engine_1.comprometerExibicao)(message.id,Date.now());canceladoRef.current||(visivelRef.current=true,setVisivel({message:message,blocos:blocos,estilo:estilo,oc:oc}),onDisplayed?.(message))})()},atraso)})(momento).catch(()=>{})};reagendarRef.current=()=>{agendarSessao().catch(()=>{})},rodarSeguro("sessao");const desinscrever=(0,lifecycle_1.inscreverInApp)(rodarSeguro);return()=>{canceladoRef.current=true,reagendarRef.current=null,timer&&clearTimeout(timer),timerSessao&&clearTimeout(timerSessao),desinscrever()}},[dono]);const fechar=(0,react_1.useCallback)(()=>{visivelRef.current=false,setVisivel(null),reagendarRef.current?.()},[]);return visivel?(0,jsx_runtime_1.jsx)(MessageView,{message:visivel.message,blocos:visivel.blocos,estilo:visivel.estilo,oc:visivel.oc,onClose:fechar}):null}function PushMeshInAppHost({onDisplayed:onDisplayed}){return(0,jsx_runtime_1.jsx)(HostInApp,{auto:false,onDisplayed:onDisplayed})}exports.PushMeshInApp=PushMeshInAppHost;const styles=react_native_1.StyleSheet.create({veil:{flex:1,justifyContent:"center",padding:24},full:{flex:1,justifyContent:"center",padding:24},banner:{position:"absolute",left:0,right:0,zIndex:9999,elevation:10,padding:8},bannerTop:{top:0},bannerBottom:{bottom:0},card:{overflow:"hidden",shadowColor:"#000",shadowOpacity:.2,shadowRadius:8,shadowOffset:{width:0,height:2},elevation:6},close:{position:"absolute",top:6,right:8,padding:6}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.lerCache=lerCache,exports.lerEstado=lerEstado,exports.comprometerExibicao=comprometerExibicao,exports.buscarPacote=buscarPacote,exports.casaGatilho=casaGatilho,exports.decidir=decidir,exports.avaliar=avaliar,exports.proximoDisparoSessaoMs=proximoDisparoSessaoMs,exports.enviarRecibo=enviarRecibo;const http_1=require("../http"),queue_1=require("../queue"),state_1=require("../state"),storage_1=require("../storage"),triggers_1=require("../triggers"),sessao_1=require("./sessao"),VALIDADE_S_PADRAO=86400,H_MS=36e5,FORMATOS=["banner_top","banner_bottom","modal","fullscreen"],SCHEMA=3;async function lerCache(){const c=await(0,storage_1.getJson)(storage_1.KEYS.inappPacote,null);return c&&c.pacote&&Array.isArray(c.pacote.messages)&&"number"==typeof c.recebido_em?c:null}async function gravarCache(pacote,recebido_em){await(0,storage_1.setJson)(storage_1.KEYS.inappPacote,{pacote:pacote,recebido_em:recebido_em})}async function lerEstado(){const e=await(0,storage_1.getJson)(storage_1.KEYS.inappEstado,null);return{global_ultima_em:e?.global_ultima_em??0,campanhas:{...e?.campanhas??{}}}}async function comprometerExibicao(campanhaId,agora){const estado=await lerEstado(),oc=(estado.campanhas[campanhaId]??{exibicoes:0,ultima_em:0,ocorrencia_n:0}).exibicoes+1;return estado.campanhas[campanhaId]={exibicoes:oc,ultima_em:agora,ocorrencia_n:oc},estado.global_ultima_em=agora,await(0,storage_1.setJson)(storage_1.KEYS.inappEstado,estado),oc}function sanear(body){const validade="number"==typeof body.validade_s&&body.validade_s>0?body.validade_s:86400,cap="number"==typeof body.cap_global_h&&body.cap_global_h>=0?body.cap_global_h:0;return{pacote_id:"string"==typeof body.pacote_id?body.pacote_id:null,messages:Array.isArray(body.messages)?body.messages:[],validade_s:validade,cap_global_h:cap,etag:"string"==typeof body.etag?body.etag:null}}function pacoteValido(cache,agora){return!!cache&&agora<=cache.recebido_em+1e3*cache.pacote.validade_s}async function buscarPacote(){const config=(0,state_1.requireConfig)(),playerId=state_1.state.playerId??await(0,storage_1.getItem)(storage_1.KEYS.playerId),agora=Date.now(),cache=await lerCache();if(!playerId)return pacoteValido(cache,agora)?cache:null;const usarEtag=pacoteValido(cache,agora)&&cache.pacote.etag?cache.pacote.etag:null,rota=`/api/v1/inapp/pending?app_id=${encodeURIComponent(config.appId)}&player_id=${encodeURIComponent(playerId)}&schema=${SCHEMA}`;try{const res=await(0,http_1.get)(rota,usarEtag?{"If-None-Match":usarEtag}:void 0);if(304===res.status)return pacoteValido(cache,agora)?cache:null;if(!res.ok)return pacoteValido(cache,agora)?cache:null;const body=res.json;if(!body||!Array.isArray(body.messages))return pacoteValido(cache,agora)?cache:null;const pacote=sanear(body);return await gravarCache(pacote,agora),{pacote:pacote,recebido_em:agora}}catch{return pacoteValido(cache,agora)?cache:null}}function acharChave(mapa,chave){const chaves=Object.keys(mapa);if(chaves.indexOf(chave)>=0)return{achou:true,valor:mapa[chave]};const alvo=chave.toLowerCase();for(const k of chaves)if(k.toLowerCase()===alvo)return{achou:true,valor:mapa[k]};return{achou:false}}function paraNumero(v){if("number"==typeof v)return Number.isFinite(v)?v:null;if("string"!=typeof v)return null;const t=v.trim();if(!t)return null;const n=Number(t);return Number.isFinite(n)?n:null}function paraTexto(v){return String(v).trim().toLowerCase()}function casaEvento(g,mapa){const chave=g.chave;if("string"!=typeof chave||!chave)return false;const{achou:achou,valor:valor}=acharChave(mapa,chave),comp=g.comparador;if("nao_existe"===comp)return!achou;if(!achou)return false;if("existe"===comp)return true;const alvo=g.valor;if("number"==typeof alvo){const local=paraNumero(valor);return null!==local&&("igual"===comp?local===alvo:"diferente"===comp?local!==alvo:"maior"===comp?local>alvo:"menor"===comp&&local<alvo)}if("string"==typeof alvo){const local=paraTexto(valor),esperado=alvo.trim().toLowerCase();return"igual"===comp?local===esperado:"diferente"===comp&&local!==esperado}return false}function casaGatilho(gatilho,ctx,agora){const tipo=gatilho?.tipo??"ao_abrir";if("ao_abrir"===tipo)return"sessao"===ctx.momento;if("inatividade"===tipo){if("sessao"!==ctx.momento||null===ctx.inatividade_h)return false;const horas=gatilho?.horas;return"number"==typeof horas&&horas>0&&ctx.inatividade_h>=horas}if("sessao_duracao"===tipo){const segundos=gatilho?.segundos;return"number"==typeof segundos&&segundos>0&&agora-ctx.inicio_em>=1e3*segundos}return"evento"===tipo&&!!gatilho&&casaEvento(gatilho,ctx.gatilhos)}function decidir(cache,estado,agora,ctx){const c=ctx??{momento:"sessao",inicio_em:agora,inatividade_h:null,gatilhos:{}},{pacote:pacote,recebido_em:recebido_em}=cache;if(agora>recebido_em+1e3*pacote.validade_s)return null;if(pacote.cap_global_h>0&&estado.global_ultima_em>0&&agora-estado.global_ultima_em<pacote.cap_global_h*H_MS)return null;for(const m of pacote.messages){if(!m||"string"!=typeof m.id)continue;if(!FORMATOS.includes(m.formato))continue;if(!casaGatilho(m.gatilho,c,agora))continue;const regras=m.regras??{};if(regras.inicia_em){const t=Date.parse(regras.inicia_em);if(Number.isFinite(t)&&agora<t)continue}if(regras.expira_em){const t=Date.parse(regras.expira_em);if(Number.isFinite(t)&&agora>t)continue}const ec=estado.campanhas[m.id],capTotal=regras.cap_total??0;if(capTotal>0&&(ec?.exibicoes??0)>=capTotal)continue;const cooldownMs=(regras.cooldown_h??0)*H_MS;if(!(ec&&ec.ultima_em>0&&agora-ec.ultima_em<cooldownMs))return m}return null}async function cacheValido(agora){const c=await lerCache();return pacoteValido(c,agora)?c:null}async function avaliar(momento="sessao"){const inicio=Date.now(),sessao="sessao"===momento?await(0,sessao_1.abrirSessao)(inicio):(0,sessao_1.sessaoAtual)();if(!sessao)return null;const cache="sessao"===momento?await buscarPacote():await cacheValido(inicio);if(!cache)return null;const[estado,gatilhos]=await Promise.all([lerEstado(),(0,triggers_1.lerGatilhos)()]),message=decidir(cache,estado,Date.now(),{momento:momento,inicio_em:sessao.inicio_em,inatividade_h:sessao.inatividade_h,gatilhos:gatilhos});return message?{message:message}:null}async function proximoDisparoSessaoMs(agora){const sessao=(0,sessao_1.sessaoAtual)();if(!sessao)return null;const cache=await cacheValido(agora);if(!cache)return null;let menor=null;for(const m of cache.pacote.messages){const g=m?.gatilho;if(!g||"sessao_duracao"!==g.tipo||"number"!=typeof g.segundos)continue;const falta=sessao.inicio_em+1e3*g.segundos-agora;falta>0&&(null===menor||falta<menor)&&(menor=falta)}return menor}async function enviarRecibo(campanhaId,prova,evento,ocorrenciaN,botao){if(!prova)return;const config=(0,state_1.requireConfig)(),playerId=state_1.state.playerId??await(0,storage_1.getItem)(storage_1.KEYS.playerId),item={campanha_id:campanhaId,prova:prova,evento:evento,ocorrencia_n:ocorrenciaN,em:(new Date).toISOString()};"clique"===evento&&botao&&(item.botao=botao),await(0,queue_1.sendOrQueue)("POST","/api/v1/inapp/eventos",{app_id:config.appId,player_id:playerId,eventos:[item]})}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.IMAGE_PREFETCH_TIMEOUT_MS=void 0,exports.ensureImage=ensureImage,exports.__clearImageCacheForTests=__clearImageCacheForTests;const react_native_1=require("react-native"),cache=new Map;function ensureImage(url,timeoutMs=exports.IMAGE_PREFETCH_TIMEOUT_MS){const cached=cache.get(url);if(cached)return cached;let timer;const prefetch=react_native_1.Image.prefetch(url).then(ok=>Boolean(ok),()=>false),timeout=new Promise(resolve=>{timer=setTimeout(()=>resolve(false),timeoutMs)}),result=Promise.race([prefetch,timeout]).finally(()=>clearTimeout(timer));return cache.set(url,result),result}function __clearImageCacheForTests(){cache.clear()}exports.IMAGE_PREFETCH_TIMEOUT_MS=4e3;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RpInAppEventType, RpInAppMessage } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* API pública do In-App (contrato V1/V2). O RENDER e a decisão device-first
|
|
4
|
+
* ficam em `components.tsx`/`engine.ts` — auto-conduzidos por `<PushMeshInApp/>`
|
|
5
|
+
* (ver README). Estas funções são a superfície clássica congelada.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Mensagens do pacote pendente para este device (GET /api/v1/inapp/pending).
|
|
9
|
+
* Nunca quebra o app: offline/erro/rota-ausente ⇒ cai para o cache ou [].
|
|
10
|
+
*/
|
|
11
|
+
export declare function pending(): Promise<RpInAppMessage[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Reporta um evento do funil (contrato §4.1). O caminho normal é AUTOMÁTICO
|
|
14
|
+
* (o renderizador envia impressão/clique/fechou com a prova e o `ocorrencia_n`
|
|
15
|
+
* corretos). Esta função clássica é um atalho manual: só envia para uma
|
|
16
|
+
* mensagem que está no pacote em cache (de onde tira a prova ECOADA e o
|
|
17
|
+
* `ocorrencia_n` local). `reativou`/`imagem_falhou` são reservados da F2 — o
|
|
18
|
+
* servidor F1 não os aceita, então aqui são no-op (não somem num 400 de lote).
|
|
19
|
+
*/
|
|
20
|
+
export declare function track(tipo: RpInAppEventType, messageId?: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.pending=pending,exports.track=track,exports.executeAction=executeAction;const react_native_1=require("react-native"),permissions_1=require("../permissions"),engine_1=require("./engine");async function pending(){const cache=await(0,engine_1.buscarPacote)();return cache?.pacote.messages??[]}async function track(tipo,messageId){if("impressao"!==tipo&&"clique"!==tipo&&"fechou"!==tipo)return;if(!messageId)return;const cache=await(0,engine_1.lerCache)(),msg=cache?.pacote.messages.find(m=>m.id===messageId);if(!msg?.prova)return;const estado=await(0,engine_1.lerEstado)(),oc=estado.campanhas[messageId]?.ocorrencia_n??1;await(0,engine_1.enviarRecibo)(messageId,msg.prova,tipo,oc)}async function executeAction(acao,campanhaId){switch(acao.tipo){case"open_url":{const url=acao.url?.trim();if(!url)return;try{await react_native_1.Linking.canOpenURL(url)&&await react_native_1.Linking.openURL(url)}catch{}return}case"open_notification_settings":return campanhaId&&await(0,permissions_1.markReativaPending)(campanhaId),void await(0,permissions_1.reativarNotificacoes)();case"dismiss":return}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.SESSAO_FOREGROUND_MS=void 0,exports.inscreverInApp=inscreverInApp,exports.aoVoltarAoForeground=aoVoltarAoForeground,exports.aoMudarGatilhos=aoMudarGatilhos,exports.__resetLifecycleForTests=__resetLifecycleForTests,exports.SESSAO_FOREGROUND_MS=3e4;const ouvintes=new Set;function inscreverInApp(cb){return ouvintes.add(cb),()=>{ouvintes.delete(cb)}}function notificar(momento){for(const cb of ouvintes)try{cb(momento)}catch{}}function aoVoltarAoForeground(emBackgroundDesde){null!==emBackgroundDesde&&(Date.now()-emBackgroundDesde<exports.SESSAO_FOREGROUND_MS||notificar("sessao"))}function aoMudarGatilhos(){notificar("durante")}function __resetLifecycleForTests(){ouvintes.clear()}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function textoNaoVazio(v){return"string"==typeof v&&""!==v.trim()?v:null}function projetarParaBlocos(conteudo,estilo){const imagem=textoNaoVazio(conteudo.imagem),titulo=textoNaoVazio(conteudo.titulo),corpo=textoNaoVazio(conteudo.corpo),botao=conteudo.botao??null,botaoSec=conteudo.botao_secundario??null,acaoCard=conteudo.acao_card??null,soImagem=null!==imagem&&null===titulo&&null===corpo&&null===botao,blocos=[];if(null!==imagem){const sangra="boolean"==typeof estilo.imagem_sangra?estilo.imagem_sangra:soImagem;blocos.push({tipo:"imagem",url:imagem,raio:0,sangra:sangra,acao_click:soImagem?acaoCard:null})}null!==titulo&&blocos.push({tipo:"texto",texto:titulo,peso:"negrito",tamanho:18}),null!==corpo&&blocos.push({tipo:"texto",texto:corpo,peso:"regular",tamanho:14});for(const b of[botao,botaoSec])b&&blocos.push({tipo:"botao",texto:b.texto,acao:b.acao});const est={...estilo};return delete est.imagem_sangra,!soImagem&&acaoCard&&(est.acao_card=acaoCard),{blocos:blocos,estilo:est}}function normalizar(message){const conteudo=message.conteudo??{},estilo=message.estilo??{};return Array.isArray(conteudo.blocos)?{blocos:conteudo.blocos,estilo:estilo}:projetarParaBlocos(conteudo,estilo)}Object.defineProperty(exports,"__esModule",{value:true}),exports.normalizar=normalizar;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.algumHostMontou=algumHostMontou,exports.ehDono=ehDono,exports.useDonoDoHost=useDonoDoHost,exports.__resetHostsForTests=__resetHostsForTests;const react_1=require("react"),state_1=require("../state");let proximoId=1,algumMontou=false,avisouDuplicado=false;const montados=[];function algumHostMontou(){return algumMontou}function ehDono(id){return id>0&&montados.length>0&&montados[0].id===id}function avisarDuplicidade(){!avisouDuplicado&&(0,state_1.emDesenvolvimento)()&&montados.some(i=>i.auto)&&montados.some(i=>!i.auto)&&(avisouDuplicado=true,console.warn("[PushMesh] o host in-app está montado DUAS vezes: o PushMesh.init() já monta o host na raiz do app, e a sua árvore monta outro à mão. Só um busca e desenha (o outro é no-op — nada de recibo duplicado). Como corrigir: você não precisa mais montar à mão, apague o <PushMeshInAppHost /> da sua árvore — ou, se prefere escolher onde ele vive, chame PushMesh.init({ appId, inAppAutoMontar: false })."))}function useDonoDoHost(auto){const[,redesenhar]=(0,react_1.useState)(0),idRef=(0,react_1.useRef)(0);return(0,react_1.useEffect)(()=>{const id=proximoId++;return montados.push({id:id,auto:auto,avisar:()=>redesenhar(n=>n+1)}),algumMontou=true,idRef.current=id,avisarDuplicidade(),redesenhar(n=>n+1),()=>{idRef.current=0;const i=montados.findIndex(m=>m.id===id);i<0||(montados.splice(i,1),0===i&&montados.length>0&&montados[0].avisar())}},[auto]),ehDono(idRef.current)}function __resetHostsForTests(){montados.length=0,proximoId=1,algumMontou=false,avisouDuplicado=false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.sessaoAtual=sessaoAtual,exports.abrirSessao=abrirSessao,exports.__resetSessaoForTests=__resetSessaoForTests;const storage_1=require("../storage"),H_MS=36e5;let atual=null;function sessaoAtual(){return atual}async function abrirSessao(agora){const p=await(0,storage_1.getJson)(storage_1.KEYS.inappSessao,null),ultima="number"==typeof p?.ultima_em&&p.ultima_em>0?p.ultima_em:0;atual={inicio_em:agora,inatividade_h:0===ultima?null:Math.max(0,agora-ultima)/H_MS};const primeira="number"==typeof p?.primeira_em&&p.primeira_em>0?p.primeira_em:agora;return await(0,storage_1.setJson)(storage_1.KEYS.inappSessao,{ultima_em:agora,primeira_em:primeira}),atual}function __resetSessaoForTests(){atual=null}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { boot } from './boot';
|
|
2
|
+
import { addNotificationListener, removeNotificationListener } from './events';
|
|
3
|
+
import { login, logout, getPlayerId, setToken } from './players';
|
|
4
|
+
import { handleClick, handlePushData, sendReceipt } from './receipts';
|
|
5
|
+
import { canRequestNatively, openNotificationSettings, readNotificationTypes } from './permissions';
|
|
6
|
+
import { checkAndReport, detachAppStateHook } from './permissions';
|
|
7
|
+
import { pending as inappPending, track as inappTrack } from './inapp';
|
|
8
|
+
import { addTag, addTags, getTags, removeTag, removeTags } from './tags';
|
|
9
|
+
import { definirGatilho, definirGatilhos, lerGatilhos, limparGatilhos, removerGatilho } from './triggers';
|
|
10
|
+
/**
|
|
11
|
+
* API pública do SDK PUSHMESH.
|
|
12
|
+
*
|
|
13
|
+
* Contrato congelado — o teste de contrato quebra se mudar a assinatura:
|
|
14
|
+
* PushMesh.init(config) · setToken · login · logout · getPlayerId
|
|
15
|
+
* PushMesh.definirGatilho(chave, valor) · removerGatilho · limparGatilhos
|
|
16
|
+
* PushMesh.receipts.handlePushData(data)
|
|
17
|
+
* PushMesh.notifications.{handleClick, addEventListener, removeEventListener}
|
|
18
|
+
* PushMesh.permissions.{checkAndReport, openNotificationSettings, canRequestNatively}
|
|
19
|
+
* PushMesh.user.{addTag, addTags, removeTag, removeTags}
|
|
20
|
+
* PushMesh.inapp.{pending, track} · PushMesh.triggers.{...}
|
|
21
|
+
*/
|
|
22
|
+
export declare const PushMesh: {
|
|
23
|
+
/**
|
|
24
|
+
* Inicializa o SDK — uma vez no boot do app. SÓ o `appId` é obrigatório:
|
|
25
|
+
* sem `baseUrl` vale o servidor oficial (https://push.pushmesh.io).
|
|
26
|
+
* Chamadas concorrentes compartilham o mesmo boot (um registro só).
|
|
27
|
+
*/
|
|
28
|
+
init: typeof boot;
|
|
29
|
+
/** Entrega o token de push (FCM) ao SDK — chamar no onTokenRefresh do host. */
|
|
30
|
+
setToken: typeof setToken;
|
|
31
|
+
/** Amarra external_user_id ao player (PUT /players/:id). */
|
|
32
|
+
login: typeof login;
|
|
33
|
+
/** Limpa external_user_id com null explícito (tri-state do servidor). */
|
|
34
|
+
logout: typeof logout;
|
|
35
|
+
/** player_id (devices.uuid) do registro, ou null se ainda não registrado. */
|
|
36
|
+
getPlayerId: typeof getPlayerId;
|
|
37
|
+
/**
|
|
38
|
+
* GATILHO NO APLICATIVO — `definirGatilho('tela', 'checkout')`: o app avisa o
|
|
39
|
+
* que está acontecendo e a mensagem in-app com esse gatilho aparece NA HORA,
|
|
40
|
+
* decidida no aparelho (sem rede). Valor: texto, número ou booleano.
|
|
41
|
+
* O mapa PERSISTE entre sessões e só o app o limpa — quem põe é quem tira
|
|
42
|
+
* (`removerGatilho('tela')` ao sair da tela).
|
|
43
|
+
*/
|
|
44
|
+
definirGatilho: typeof definirGatilho;
|
|
45
|
+
/** Vários gatilhos de uma vez (mescla com os que já existem). */
|
|
46
|
+
definirGatilhos: typeof definirGatilhos;
|
|
47
|
+
/** Tira um gatilho do mapa (ex.: ao sair da tela que o definiu). */
|
|
48
|
+
removerGatilho: typeof removerGatilho;
|
|
49
|
+
/** Esvazia o mapa de gatilhos do app. */
|
|
50
|
+
limparGatilhos: typeof limparGatilhos;
|
|
51
|
+
/** Mapa atual de gatilhos do app (leitura local). */
|
|
52
|
+
lerGatilhos: typeof lerGatilhos;
|
|
53
|
+
receipts: {
|
|
54
|
+
/**
|
|
55
|
+
* Processa o `data` de um push recebido e manda o recibo de entrega se
|
|
56
|
+
* houver pm_msg_id + pm_rcpt. Dispara 'foregroundWillDisplay' (dedup LRU).
|
|
57
|
+
*/
|
|
58
|
+
handlePushData: typeof handlePushData;
|
|
59
|
+
sendReceipt: typeof sendReceipt;
|
|
60
|
+
};
|
|
61
|
+
notifications: {
|
|
62
|
+
/** Usuário tocou na notificação — dispara 'click' e garante o recibo. */
|
|
63
|
+
handleClick: typeof handleClick;
|
|
64
|
+
addEventListener: typeof addNotificationListener;
|
|
65
|
+
removeEventListener: typeof removeNotificationListener;
|
|
66
|
+
};
|
|
67
|
+
permissions: {
|
|
68
|
+
/** Lê a permissão real e reporta ao servidor se mudou. */
|
|
69
|
+
checkAndReport: typeof checkAndReport;
|
|
70
|
+
/** Abre a tela de notificações do app nos Ajustes do sistema. */
|
|
71
|
+
openNotificationSettings: typeof openNotificationSettings;
|
|
72
|
+
/** true se ainda dá para pedir a permissão nativa (botão inteligente). */
|
|
73
|
+
canRequestNatively: typeof canRequestNatively;
|
|
74
|
+
/** Lê o notification_types atual sem reportar. */
|
|
75
|
+
readNotificationTypes: typeof readNotificationTypes;
|
|
76
|
+
};
|
|
77
|
+
user: {
|
|
78
|
+
/** Tags de segmentação — PUT players com o mapa completo (replace). */
|
|
79
|
+
addTag: typeof addTag;
|
|
80
|
+
addTags: typeof addTags;
|
|
81
|
+
removeTag: typeof removeTag;
|
|
82
|
+
removeTags: typeof removeTags;
|
|
83
|
+
getTags: typeof getTags;
|
|
84
|
+
};
|
|
85
|
+
inapp: {
|
|
86
|
+
/** Mensagens do pacote in-app pendente (offline/erro = cai para o cache/[]). */
|
|
87
|
+
pending: typeof inappPending;
|
|
88
|
+
/**
|
|
89
|
+
* Reporte manual do funil in-app: impressao / clique / fechou (o render
|
|
90
|
+
* automático já os envia). reativou / imagem_falhou são reservados da F2.
|
|
91
|
+
*/
|
|
92
|
+
track: typeof inappTrack;
|
|
93
|
+
};
|
|
94
|
+
triggers: {
|
|
95
|
+
/**
|
|
96
|
+
* Mesmos gatilhos de `definirGatilho`/`removerGatilho` com os nomes
|
|
97
|
+
* clássicos — mesmo mapa, mesma reavaliação na hora.
|
|
98
|
+
*/
|
|
99
|
+
addTrigger: typeof definirGatilho;
|
|
100
|
+
addTriggers: typeof definirGatilhos;
|
|
101
|
+
removeTrigger: typeof removerGatilho;
|
|
102
|
+
removeTriggers: typeof import("./triggers").removerGatilhos;
|
|
103
|
+
clearTriggers: typeof limparGatilhos;
|
|
104
|
+
getTriggers: typeof lerGatilhos;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Diagnóstico no device (espelho do bin pushmesh-doctor): imprime um
|
|
108
|
+
* relatório com "como corrigir" para cada falha. Útil em builds de QA.
|
|
109
|
+
*/
|
|
110
|
+
doctor(): Promise<void>;
|
|
111
|
+
};
|
|
112
|
+
export { PushMeshInAppHost, PushMeshInApp } from './inapp/components';
|
|
113
|
+
export { RpDeviceType, RpNotificationTypes } from './types';
|
|
114
|
+
export type { RpValorGatilho } from './triggers';
|
|
115
|
+
export type { PushMeshConfig, RpInAppAction, RpInAppButton, RpInAppEventType, RpInAppGatilho, RpInAppMessage, RpInAppPacote, } from './types';
|
|
116
|
+
export type { RpNotificationEvent, RpNotificationEventType, RpNotificationListener } from './events';
|
|
117
|
+
export { detachAppStateHook };
|
|
118
|
+
export default PushMesh;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.detachAppStateHook=exports.RpNotificationTypes=exports.RpDeviceType=exports.PushMeshInApp=exports.PushMeshInAppHost=exports.PushMesh=void 0;const boot_1=require("./boot"),events_1=require("./events"),players_1=require("./players"),receipts_1=require("./receipts"),permissions_1=require("./permissions"),permissions_2=require("./permissions");Object.defineProperty(exports,"detachAppStateHook",{enumerable:true,get:function(){return permissions_2.detachAppStateHook}});const inapp_1=require("./inapp"),tags_1=require("./tags"),triggers_1=require("./triggers"),state_1=require("./state"),storage_1=require("./storage");exports.PushMesh={init:boot_1.boot,setToken:players_1.setToken,login:players_1.login,logout:players_1.logout,getPlayerId:players_1.getPlayerId,definirGatilho:triggers_1.definirGatilho,definirGatilhos:triggers_1.definirGatilhos,removerGatilho:triggers_1.removerGatilho,limparGatilhos:triggers_1.limparGatilhos,lerGatilhos:triggers_1.lerGatilhos,receipts:{handlePushData:receipts_1.handlePushData,sendReceipt:receipts_1.sendReceipt},notifications:{handleClick:receipts_1.handleClick,addEventListener:events_1.addNotificationListener,removeEventListener:events_1.removeNotificationListener},permissions:{checkAndReport:permissions_2.checkAndReport,openNotificationSettings:permissions_1.openNotificationSettings,canRequestNatively:permissions_1.canRequestNatively,readNotificationTypes:permissions_1.readNotificationTypes},user:{addTag:tags_1.addTag,addTags:tags_1.addTags,removeTag:tags_1.removeTag,removeTags:tags_1.removeTags,getTags:tags_1.getTags},inapp:{pending:inapp_1.pending,track:inapp_1.track},triggers:{addTrigger:triggers_1.addTrigger,addTriggers:triggers_1.addTriggers,removeTrigger:triggers_1.removeTrigger,removeTriggers:triggers_1.removeTriggers,clearTriggers:triggers_1.clearTriggers,getTriggers:triggers_1.getTriggers},async doctor(){const linhas=["pushmesh-doctor (on-device)"],cfg=state_1.state.config;linhas.push(cfg?`OK config: appId=${cfg.appId} baseUrl=${cfg.baseUrl}`:"FALHA config: init() não chamado — como corrigir: PushMesh.init({ appId }) no boot.");const token=state_1.state.token??await(0,storage_1.getItem)(storage_1.KEYS.token);linhas.push(token?"OK token de push presente":"FALHA token ausente — como corrigir: config.getToken no init, PushMesh.setToken(token) no onTokenRefresh, ou cadastre o google-services.json no painel (zero-config Android).");const playerId=state_1.state.playerId??await(0,storage_1.getItem)(storage_1.KEYS.playerId);linhas.push(playerId?`OK player registrado: ${playerId}`:"FALHA player não registrado — como corrigir: confira rede/baseUrl e chame init novamente; o registro é retentado a cada boot.");try{const nt=await(0,permissions_1.readNotificationTypes)();linhas.push(1===nt?"OK permissão de notificação concedida (notification_types=1)":-2===nt?"FALHA notificações bloqueadas (-2) — como corrigir: PushMesh.permissions.openNotificationSettings() e orientar o usuário.":"INFO permissão nunca solicitada (0) — como corrigir: campanha Reativa com prompt nativo (canRequestNatively=true).")}catch(e){linhas.push(`FALHA leitura de permissão: ${String(e)}`)}console.log(linhas.join("\n"))}};var components_1=require("./inapp/components");Object.defineProperty(exports,"PushMeshInAppHost",{enumerable:true,get:function(){return components_1.PushMeshInAppHost}}),Object.defineProperty(exports,"PushMeshInApp",{enumerable:true,get:function(){return components_1.PushMeshInApp}});var types_1=require("./types");Object.defineProperty(exports,"RpDeviceType",{enumerable:true,get:function(){return types_1.RpDeviceType}}),Object.defineProperty(exports,"RpNotificationTypes",{enumerable:true,get:function(){return types_1.RpNotificationTypes}}),exports.default=exports.PushMesh;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PERFIL DO APARELHO — nível GRATUITO de rastreamento.
|
|
3
|
+
*
|
|
4
|
+
* Regra que define "gratuito" (decisão de produto, 2026-07-29): campo que **não
|
|
5
|
+
* pede permissão ao usuário** e **não altera a ficha de privacidade do app do
|
|
6
|
+
* cliente** nas lojas. Por isso ficam DE FORA, mesmo sendo tecnicamente
|
|
7
|
+
* possíveis: id de publicidade (exige `AD_ID` declarada na Play e o diálogo
|
|
8
|
+
* ATT no iOS) e localização.
|
|
9
|
+
*
|
|
10
|
+
* Nada aqui pode derrubar ou atrasar o registro: TUDO é best-effort e cada
|
|
11
|
+
* campo falha sozinho. Um aparelho exótico que quebre uma leitura registra
|
|
12
|
+
* igual, só que com aquele campo vazio.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Versão do SDK — reportada em `sdk_versao` para diagnosticar campo sem chute.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ Espelho de `version` do package.json: a duplicação é imposta pelo build e
|
|
18
|
+
* travada por `__tests__/versao.test.ts` (o porquê das duas coisas está lá,
|
|
19
|
+
* fora do pacote publicado). `npm version <patch|minor|major>` sincroniza esta
|
|
20
|
+
* linha sozinho (script `version` → scripts/sync-versao.mjs); editando à mão,
|
|
21
|
+
* suba as duas.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SDK_VERSAO = "0.7.2";
|
|
24
|
+
export interface PerfilDispositivo {
|
|
25
|
+
device_os?: string;
|
|
26
|
+
fabricante?: string;
|
|
27
|
+
modelo?: string;
|
|
28
|
+
sdk_versao?: string;
|
|
29
|
+
timezone_id?: string;
|
|
30
|
+
pais?: string;
|
|
31
|
+
net_type?: string;
|
|
32
|
+
carrier?: string;
|
|
33
|
+
rooted?: boolean;
|
|
34
|
+
sessoes?: number;
|
|
35
|
+
/** Android: fila de standby (throttling de entrega dito pelo sistema). */
|
|
36
|
+
standby_bucket?: string;
|
|
37
|
+
/** Android: app isento da otimização de bateria (OEM não mata o processo). */
|
|
38
|
+
bateria_irrestrita?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* BITMASK granular da permissão: 1=habilitadas · 2=alerta · 4=som ·
|
|
41
|
+
* 8=badge · 16=tela de bloqueio · 32=central/canal. Diagnostica "tenho
|
|
42
|
+
* permissão mas não vejo nada".
|
|
43
|
+
*/
|
|
44
|
+
notif_permissoes?: number;
|
|
45
|
+
/** Hash do id do aparelho — liga tokens do MESMO aparelho entre reinstalações. */
|
|
46
|
+
instalacao_id?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Monta o perfil. Chamado uma vez por init, ANTES do registro.
|
|
50
|
+
* Nunca lança — no pior caso devolve só o que o JS conseguiu.
|
|
51
|
+
*/
|
|
52
|
+
export declare function coletarPerfil(): Promise<PerfilDispositivo>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true}),exports.SDK_VERSAO=void 0,exports.coletarPerfil=coletarPerfil;const react_native_1=require("react-native"),storage_1=require("./storage");function moduloPerfil(){const mods=react_native_1.NativeModules;return"ios"===react_native_1.Platform.OS?mods.PushMeshApns:mods.PushMeshFirebase}function timezoneId(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||void 0}catch{return}}function pais(){try{const loc=Intl.DateTimeFormat().resolvedOptions().locale,m=/[-_]([A-Za-z]{2})(?:[-_]|$)/.exec(loc);return m?m[1].toUpperCase():void 0}catch{return}}async function proximaSessao(){const atual=Number(await(0,storage_1.getItem)(storage_1.KEYS.sessoes)??"0"),proxima=Number.isFinite(atual)&&atual>=0?atual+1:1;return await(0,storage_1.setItem)(storage_1.KEYS.sessoes,String(proxima)),proxima}function semVazios(p){const out={};for(const[k,v]of Object.entries(p))null!=v&&""!==v&&(out[k]=v);return out}async function coletarPerfil(){const perfil={sdk_versao:exports.SDK_VERSAO,timezone_id:timezoneId(),pais:pais()};try{perfil.sessoes=await proximaSessao()}catch{}try{const nativo=moduloPerfil();if(nativo?.perfilDispositivo){const n=await nativo.perfilDispositivo();Object.assign(perfil,{device_os:n.device_os,fabricante:n.fabricante,modelo:n.modelo,net_type:n.net_type,carrier:n.carrier,rooted:n.rooted,standby_bucket:n.standby_bucket,bateria_irrestrita:n.bateria_irrestrita,notif_permissoes:n.notif_permissoes,instalacao_id:n.instalacao_id})}}catch{}if(!perfil.device_os){const v=react_native_1.Platform.Version;perfil.device_os="string"==typeof v?v:String(v)}if(!perfil.fabricante){const c=react_native_1.Platform.constants;perfil.fabricante="ios"===react_native_1.Platform.OS?"Apple":c?.Manufacturer}return semVazios(perfil)}exports.SDK_VERSAO="0.7.2";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AuthorizationStatus } from '@notifee/react-native';
|
|
2
|
+
import { RpNotificationTypes } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Base do módulo Reativa — reengajar quem bloqueou ou nunca ligou notificações.
|
|
5
|
+
*
|
|
6
|
+
* Lê o estado REAL da permissão via notifee, mapeia para notification_types
|
|
7
|
+
* (1 ativo / -2 bloqueado / 0 nunca perguntou) e reporta
|
|
8
|
+
* via PUT /players/:id SOMENTE quando muda (cache do último estado).
|
|
9
|
+
*
|
|
10
|
+
* O hook de AppState re-checa ao voltar para `active` — é o que detecta a
|
|
11
|
+
* "reativou" (usuário foi aos Ajustes e ligou a notificação).
|
|
12
|
+
*/
|
|
13
|
+
/** Mapeia o authorizationStatus do notifee para notification_types. */
|
|
14
|
+
export declare function mapAuthorizationStatus(status: AuthorizationStatus): RpNotificationTypes;
|
|
15
|
+
/** Lê o estado real da permissão no sistema operacional. */
|
|
16
|
+
export declare function readNotificationTypes(): Promise<RpNotificationTypes>;
|
|
17
|
+
/**
|
|
18
|
+
* O "botão inteligente": true quando ainda dá para pedir a
|
|
19
|
+
* permissão NATIVA (iOS notDetermined, ou Android 13+ sem decisão final).
|
|
20
|
+
* false → o caminho é abrir os Ajustes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function canRequestNatively(): Promise<boolean>;
|
|
23
|
+
/** Abre a tela de notificações do app nos Ajustes do sistema. */
|
|
24
|
+
export declare function openNotificationSettings(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* O caminho HONESTO de reativação, usado pelo botão do in-app e pela
|
|
27
|
+
* API clássica — UMA implementação só, de propósito: quando havia duas, o
|
|
28
|
+
* in-app pulava direto para os Ajustes e o usuário caía na RAIZ dos Ajustes num
|
|
29
|
+
* app que nunca tinha pedido permissão.
|
|
30
|
+
*
|
|
31
|
+
* Por que a ordem importa: se o sistema ainda aceita o prompt NATIVO, mandar a
|
|
32
|
+
* pessoa para os Ajustes é péssimo — some a conversão de um toque em "Permitir",
|
|
33
|
+
* e pior, um app que nunca pediu permissão SEQUER TEM página nos Ajustes do
|
|
34
|
+
* iOS, então o sistema abre a raiz e a pessoa se perde.
|
|
35
|
+
*
|
|
36
|
+
* @returns true se, ao final, a permissão está concedida.
|
|
37
|
+
*/
|
|
38
|
+
export declare function reativarNotificacoes(): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Lê a permissão e reporta ao servidor SE mudou desde o último reporte.
|
|
41
|
+
* Detecta a virada (reativação): se havia campanha Reativa aguardando e o
|
|
42
|
+
* estado melhorou para ativo, dispara o evento `reativou` (fila offline).
|
|
43
|
+
*/
|
|
44
|
+
export declare function checkAndReport(): Promise<RpNotificationTypes>;
|
|
45
|
+
/** Marca que o usuário saiu para os Ajustes a partir de uma campanha Reativa. */
|
|
46
|
+
export declare function markReativaPending(messageId: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Hook de AppState: ao voltar para `active`, re-checa permissão (detecta a
|
|
49
|
+
* reativação) e esgota a fila offline. Chamado uma vez no init.
|
|
50
|
+
* Idempotente: chamadas repetidas não duplicam a assinatura.
|
|
51
|
+
*/
|
|
52
|
+
export declare function attachAppStateHook(): void;
|
|
53
|
+
/** Remove o hook (hot reload / testes). */
|
|
54
|
+
export declare function detachAppStateHook(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?function(o,m,k,k2){void 0===k2&&(k2=k);var desc=Object.getOwnPropertyDescriptor(m,k);desc&&!("get"in desc?!m.__esModule:desc.writable||desc.configurable)||(desc={enumerable:true,get:function(){return m[k]}}),Object.defineProperty(o,k2,desc)}:function(o,m,k,k2){void 0===k2&&(k2=k),o[k2]=m[k]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(o,v){Object.defineProperty(o,"default",{enumerable:true,value:v})}:function(o,v){o.default=v}),__importStar=this&&this.__importStar||function(){var ownKeys=function(o){return ownKeys=Object.getOwnPropertyNames||function(o){var ar=[];for(var k in o)Object.prototype.hasOwnProperty.call(o,k)&&(ar[ar.length]=k);return ar},ownKeys(o)};return function(mod){if(mod&&mod.__esModule)return mod;var result={};if(null!=mod)for(var k=ownKeys(mod),i=0;i<k.length;i++)"default"!==k[i]&&__createBinding(result,mod,k[i]);return __setModuleDefault(result,mod),result}}();Object.defineProperty(exports,"__esModule",{value:true}),exports.mapAuthorizationStatus=mapAuthorizationStatus,exports.readNotificationTypes=readNotificationTypes,exports.canRequestNatively=canRequestNatively,exports.openNotificationSettings=openNotificationSettings,exports.reativarNotificacoes=reativarNotificacoes,exports.checkAndReport=checkAndReport,exports.markReativaPending=markReativaPending,exports.attachAppStateHook=attachAppStateHook,exports.detachAppStateHook=detachAppStateHook;const react_native_1=__importStar(require("@notifee/react-native")),react_native_2=require("react-native"),queue_1=require("./queue"),state_1=require("./state"),storage_1=require("./storage"),types_1=require("./types");function mapAuthorizationStatus(status){switch(status){case react_native_1.AuthorizationStatus.AUTHORIZED:case react_native_1.AuthorizationStatus.PROVISIONAL:return types_1.RpNotificationTypes.Ativo;case react_native_1.AuthorizationStatus.DENIED:return types_1.RpNotificationTypes.Bloqueado;case react_native_1.AuthorizationStatus.NOT_DETERMINED:default:return types_1.RpNotificationTypes.NuncaPerguntou}}async function readNotificationTypes(){return mapAuthorizationStatus((await react_native_1.default.getNotificationSettings()).authorizationStatus)}async function canRequestNatively(){return(await react_native_1.default.getNotificationSettings()).authorizationStatus===react_native_1.AuthorizationStatus.NOT_DETERMINED&&("ios"===react_native_2.Platform.OS||"android"===react_native_2.Platform.OS&&Number(react_native_2.Platform.Version)>=33)}async function openNotificationSettings(){if("android"===react_native_2.Platform.OS)return void await react_native_1.default.openNotificationSettings();const nativo=react_native_2.NativeModules.PushMeshApns;if(nativo?.abrirAjustesDeNotificacao)try{if(await nativo.abrirAjustesDeNotificacao())return}catch{}await react_native_2.Linking.openSettings()}async function reativarNotificacoes(){return await canRequestNatively()?await react_native_1.default.requestPermission():await openNotificationSettings(),1===await checkAndReport()}async function checkAndReport(){const current=await readNotificationTypes(),previousRaw=await(0,storage_1.getItem)(storage_1.KEYS.notificationTypes),previous=null===previousRaw?null:Number(previousRaw);if(previous===current)return current;const playerId=state_1.state.playerId??await(0,storage_1.getItem)(storage_1.KEYS.playerId);if(playerId&&state_1.state.config){const{sendOrQueue:sendOrQueue}=await Promise.resolve().then(()=>__importStar(require("./queue")));await sendOrQueue("PUT",`/api/v1/players/${playerId}`,{app_id:(0,state_1.requireConfig)().appId,notification_types:current}),await(0,storage_1.setItem)(storage_1.KEYS.notificationTypes,String(current))}if(current===types_1.RpNotificationTypes.Ativo&&null!==previous&&previous!==types_1.RpNotificationTypes.Ativo){const reativaPending=await(0,storage_1.getItem)(storage_1.KEYS.reativaPending);if(reativaPending){const{track:track}=await Promise.resolve().then(()=>__importStar(require("./inapp")));await track("reativou",reativaPending),await(0,storage_1.setItem)(storage_1.KEYS.reativaPending,"")}}return current}async function markReativaPending(messageId){await(0,storage_1.setItem)(storage_1.KEYS.reativaPending,messageId)}function attachAppStateHook(){state_1.state.appStateSubscription||(state_1.state.appStateSubscription=react_native_2.AppState.addEventListener("change",nextState=>{if("active"===nextState){const bgDesde=state_1.state.emBackgroundDesde;state_1.state.emBackgroundDesde=null,checkAndReport().catch(e=>console.warn("[PushMesh] checkAndReport falhou",e)),(0,queue_1.flushQueue)().catch(()=>{}),Promise.resolve().then(()=>__importStar(require("./inapp/lifecycle"))).then(m=>m.aoVoltarAoForeground(bgDesde)).catch(()=>{})}else null===state_1.state.emBackgroundDesde&&(state_1.state.emBackgroundDesde=Date.now())}))}function detachAppStateHook(){state_1.state.appStateSubscription?.remove(),state_1.state.appStateSubscription=null}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type PushMeshConfig } from './types';
|
|
2
|
+
export declare function buildRegisterPayload(): Record<string, unknown>;
|
|
3
|
+
/** Registra (ou atualiza) o device no servidor. Idempotente via hash do payload. */
|
|
4
|
+
export declare function register(): Promise<string | null>;
|
|
5
|
+
/**
|
|
6
|
+
* Login: amarra external_user_id ao player (ex.: CPF).
|
|
7
|
+
* PUT /api/v1/players/:id — o servidor honra o tri-state.
|
|
8
|
+
*/
|
|
9
|
+
export declare function login(externalUserId: string): Promise<void>;
|
|
10
|
+
/** Logout: limpa com null EXPLÍCITO (o servidor distingue "ausente" de "limpar"). */
|
|
11
|
+
export declare function logout(): Promise<void>;
|
|
12
|
+
/** init: configura, hidrata o estado, pega o token, registra e esvazia a fila. */
|
|
13
|
+
export declare function init(config: PushMeshConfig): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* setToken: chamado pelo app host quando o Firebase entrega/atualiza o token
|
|
16
|
+
* (equivalente ao onTokenRefresh do blueprint §7). Re-registra se mudou.
|
|
17
|
+
*/
|
|
18
|
+
export declare function setToken(token: string): Promise<void>;
|
|
19
|
+
/** player_id atual (devices.uuid) — útil para o app host logar/depurar. */
|
|
20
|
+
export declare function getPlayerId(): Promise<string | null>;
|