npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@3apps/fcm-notifications

v1.0.0

Published

Notificações push automáticas por segmento (país ou idioma) com Firebase: Remote Config → Cloud Scheduler → Cloud Tasks → FCM, no fuso de cada segmento.

Readme

@3apps/fcm-notifications

Notificações push automáticas com Firebase, por segmento (país ou idioma), no fuso de cada segmento. O conteúdo e os horários ficam no Remote Config; a lib cria as Cloud Functions que agendam e enviam.

Cloud Scheduler (criado pelo deploy, uma vez por dia)
  └─▶ fcmScheduleNotification ── lê o Remote Config (template de SERVIDOR)
        └─▶ Cloud Tasks: fila fcmSendNotification (uma task por disparo, no horário local do segmento)
              └─▶ fcmSendNotification ──▶ FCM, tópico do segmento (ex.: score_good_US)

Uso

Na pasta das functions do app (Firebase Functions v2, Node 20+):

npm install @3apps/fcm-notifications firebase-admin firebase-functions
// index.js
const { createNotificationWorkflow } = require('@3apps/fcm-notifications');

module.exports = createNotificationWorkflow(require('./notifications.config'));
// notifications.config.js
module.exports = {
  projectId: 'meu-projeto',
  serviceAccount: '[email protected]',
  topics: ['all', 'score_poor', 'score_fair', 'score_good', 'score_very_good'],
  topicName: (topic, segment) => `${topic}_${segment}`,
  android: { channelId: 'high_importance_channel', icon: 'ic_stat_notification', color: '#009E83' },
};

O index.js exporta as duas funções com os nomes de functionNames. Se o app tiver outras funções no mesmo codebase, espalhe o objeto: Object.assign(exports, createNotificationWorkflow(...)).

Configuração

| Campo | Obrigatório | Padrão | Para que serve | |---|---|---|---| | projectId | sim | | Projeto Firebase. Usado pela CLI. | | serviceAccount | sim | | Conta de serviço que executa as funções, assina o token OIDC do job e das tasks e é a única que pode enfileirar e invocar o envio. | | topics | sim | | Tópicos base que o app assina. Tópicos de fora da lista são ignorados com aviso. | | topicName | | (topic, segment) => \${topic}${segment}`| Nome do tópico FCM.segment é o código da chave em maiúsculas (US, PT). Precisa ser igual à regra do app. | | android| | nenhum |channelId, iconecolorda notificação no Android. Sem ícone, o Android usa o ícone do app, que costuma virar um quadrado branco. | |region| |us-central1| Região das funções e da fila. | |maxInstances| |10| | |functionNames| |{ schedule: 'fcmScheduleNotification', send: 'fcmSendNotification' }| O nome da função de envio é também o nome da fila. | |job| |{ schedule: '0 0 * * *', timeZone: 'America/Sao_Paulo', retryCount: 3, minBackoffSeconds: 300, timeoutSeconds: 540 }| Quando o job roda e como repete em caso de erro. | |send| |{ maxAttempts: 3, minBackoffSeconds: 60 }| Tentativas de envio por task. | |lookaheadHours| |25| Janela de cada execução. Precisa ser maior que o intervalo entre execuções do job (ver abaixo). | |remoteConfig| |{ keyPrefix: 'fcm_notifications', dir: 'remote-config' }| Prefixo das chaves e pasta dos JSONs no app (relativa aonotifications.config.js`). |

Remote Config

Cada segmento tem uma chave do tipo JSON no template de servidor (Remote Config → aba Servidor), <keyPrefix><código de 2 letras minúsculas>: fcm_notifications_us, fcm_notifications_pt. O job descobre os segmentos pelas chaves: para incluir um segmento, basta criar a chave.

{
  "enabled": true,
  "time_zone": "America/New_York",
  "topics": {
    "score_good": {
      "schedules": [12, "19:30"],
      "notifications": [
        {
          "title": "Good credit opens doors",
          "body": "See the cards that match a Good credit profile.",
          "data": { "open_feature": "ranking_cards" },
          "analytics_label": "opcional"
        }
      ]
    }
  }
}

| Campo | Regra | |---|---| | enabled | false pausa o segmento sem apagar o conteúdo. Se omitido, vale true. | | time_zone | Fuso IANA. Os schedules são horários locais desse fuso, com horário de verão. | | topics | Tópicos base (de config.topics). O tópico FCM é config.topicName(tópico, segmento). | | schedules | Hora cheia (12) ou "HH:MM". | | notifications | title e body obrigatórios. Cada horário de um mesmo dia recebe uma notificação diferente, com unicidade pelo título; horários que sobrarem ficam sem disparo. | | data | Enviado como data da mensagem FCM (valores viram texto). É onde vai o deep link do app. | | analytics_label | Opcional, [a-zA-Z0-9-_.~%]{1,50}. Se omitido, é derivado do tópico + título, para os relatórios do FCM separarem segmentos com o mesmo texto. |

Mantenha os JSONs numa pasta do app (remote-config/) e publique com a CLI: assim eles ficam versionados, validados e testáveis.

Como o agendamento funciona

  • Uma execução por dia, janela de 25h. Cada execução enfileira todos os horários das próximas 24h de cada segmento, no fuso dele. Com segmentos em fusos diferentes, "o resto do dia" não serve: num mesmo instante, cada um está numa hora diferente. Uma janela de 24h contém cada horário uma vez, e as execuções seguidas se encaixam, inclusive nos dias de horário de verão. A hora a mais cobre atraso ou retry do job (um horário que caia no instante exato do job já foi agendado pela execução anterior).
  • Sem disparo duplicado. O id de cada task é fixo para o disparo: hash + tópico + data local + horário. Rodar de novo, forçar o job ou o retry do Scheduler não duplicam: a Cloud Tasks recusa o id repetido (functions/task-already-exists), e o resumo conta como "id já usado". O formato do id faz parte do contrato da lib: mudar o formato é versão major.
  • Sem notificação repetida no dia. A ordem das notificações de cada dia é embaralhada com a semente tópico + data, então os horários de um mesmo dia não repetem notificação, mesmo quando são agendados por execuções diferentes.
  • Erros. JSON inválido ou task não criada falham a execução (aparece no Cloud Scheduler e o retry entra), mas os outros segmentos são agendados. Payload inválido no envio é registrado e descartado sem retry; erro do FCM é repetido pela Cloud Tasks até send.maxAttempts.

CLI

Na pasta das functions do app, com as credenciais locais (gcloud auth application-default login):

npx fcm-notifications dry-run                     # disparos da janela com o Remote Config de produção
npx fcm-notifications dry-run --local --hours=48  # com os JSONs locais e outra janela
npx fcm-notifications publish-remote-config            # prévia campo a campo (publicado → local)
npx fcm-notifications publish-remote-config --apply    # publica as chaves <keyPrefix>*
npx fcm-notifications send-push --token=<token>                    # push de teste num device
npx fcm-notifications send-push --topic=score_good_US --validate-only  # só valida no FCM
  • publish-remote-config valida cada JSON com a regra do job, só mexe nas chaves dos segmentos e preserva o resto do template. Se a última versão foi editada no console, recusa publicar (para não desfazer a edição): leve a mudança para o arquivo ou use --overwrite-console.
  • send-push aceita --title=, --body= e --data='{"chave":"valor"}'. Tópico real só com --validate-only, para um teste não virar push para todo mundo.
  • Use --config=caminho se o arquivo não for notifications.config.js.

Deploy num projeto novo

PROJECT=meu-projeto
SA=fcm-notifications@$PROJECT.iam.gserviceaccount.com

# 1. Conta de serviço (o deploy falha sem ela)
gcloud iam service-accounts create fcm-notifications --project=$PROJECT
for ROLE in roles/cloudconfig.viewer roles/firebasecloudmessaging.admin; do
  gcloud projects add-iam-policy-binding $PROJECT --member=serviceAccount:$SA --role=$ROLE --condition=None
done
gcloud iam service-accounts add-iam-policy-binding $SA \
  --member=serviceAccount:$SA --role=roles/iam.serviceAccountUser --project=$PROJECT

# 2. Conteúdo no Remote Config de servidor
npx fcm-notifications publish-remote-config --apply

# 3. Deploy (cria as funções, o job e a fila)
firebase deploy --only functions

# 4. Uma vez, na fila recém-criada (ver "Problemas conhecidos")
gcloud tasks queues add-iam-policy-binding fcmSendNotification --location=us-central1 \
  --member=serviceAccount:$SA --role=roles/cloudtasks.enqueuer --project=$PROJECT

O papel do Remote Config é roles/cloudconfig.viewer ("Firebase Remote Config Viewer"); não existe roles/firebaseremoteconfig.viewer.

Problemas conhecidos

  • Build sem permissão em projeto novo: o Cloud Build usa a conta padrão do Compute, que vem sem papéis. Dê roles/cloudbuild.builds.builder a <número>[email protected].
  • Função em estado FAILED depois de um build com erro: os deploys seguintes respondem Precondition failed. Apague a função e faça o deploy de novo; se o firebase functions:delete do job parar no 404 do Scheduler, use gcloud functions delete.
  • Fila sem enfileirador: o Firebase CLI quebra ao dar o papel de enfileirador numa fila recém-criada (Cannot read properties of undefined (reading 'filter'), bug do setEnqueuer do firebase-tools com política vazia). As funções ficam ativas, mas o job não enfileira. É o passo 4 acima; nos deploys seguintes não se repete.
  • Não apague tasks para testar. A Cloud Tasks reserva o id de uma task apagada ou executada por até 24 horas (a documentação do firebase-admin ainda fala em ~1 hora, desatualizada), e nem limpar a fila libera. Enquanto isso, o job não consegue recriar esses disparos. Para testar, use dry-run.
  • firebase-admin 14 não instala o @google-cloud/firestore, e importar firebase-functions/v2 (o índice) carrega o provider do Firestore. A lib importa só os módulos que usa (/v2/scheduler, /v2/tasks); faça o mesmo no app.

Desenvolvimento da lib

npm test          # testes, sem rede
npm version minor # ou patch/major; cria a tag no git
npm publish

firebase-admin e firebase-functions são peerDependencies: a lib usa as versões instaladas no app. Para testar uma mudança num app antes de publicar, instale a pasta como cópia (e não link), para as dependências virem do app: npm install --install-links ../caminho/fcm-notifications.