PWA com o calendário de testes, entregas e eventos de LCC, com avisos por notificação push.
- Instalável no ecrã principal (Android, iPhone e computador)
- Funciona sem ligação, com as últimas datas guardadas
- Avisos dos testes com estrela: um por dia, às 10h em Lisboa, desde uma semana antes até ao próprio dia. Dar uma estrela liga os avisos; só é preciso ir aos filtros para os desligar
npm install
cp .env.example .env.local
npm run devSem as variáveis do Redis, as subscrições ficam em memória e perdem-se ao reiniciar.
Para disparar o cron à mão (?force=1 ignora a hora e os avisos já entregues):
curl -H "Authorization: Bearer $CRON_SECRET" "http://localhost:3000/api/cron/notify?force=1"- Importar o repositório em vercel.com.
- Em Storage ou Marketplace, criar uma base de dados Upstash Redis e ligá-la ao projeto. Isto cria
KV_REST_API_URLeKV_REST_API_TOKEN. - Em Settings, Environment Variables, adicionar para o ambiente Production:
NEXT_PUBLIC_VAPID_PUBLIC_KEYVAPID_PRIVATE_KEYVAPID_SUBJECT, com um contacto realCRON_SECRET
- Fazer redeploy, porque as variáveis só entram num deploy novo.
- Abrir
/api/push/healthe confirmar"ok": true.
Mudar as chaves VAPID invalida as subscrições existentes. A app renova a subscrição da pessoa da próxima vez que for aberta.
Os crons correm às 09:00, 10:00 e 11:00 UTC. O código envia a partir das 10h em Lisboa, o que funciona na hora de inverno e de verão. As chamadas seguintes só tratam dos envios que falharam ou de crons perdidos, sem repetir o aviso a quem já o recebeu.
No plano Hobby, a Vercel pode executar um cron em qualquer minuto da hora marcada, por isso os crons da Vercel servem apenas de segurança.
O envio às horas certas vem de um agendador externo. Em cron-job.org (gratuito), criar um cron com:
- URL:
https://URL-DA-APP/api/cron/notify?secret=<CRON_SECRET> - Horário: todos os dias às 10:00, fuso
Europe/Lisbon
Os crons da Vercel podem ficar ativos como segurança: quem já recebeu o aviso não o recebe outra vez.
Quando um aviso não chega, abrir /api/push/health e ver lastCron. O recentCrons guarda as últimas 15 execuções, e byService separa os resultados por serviço de push (apple é iPhone, google é Android ou Chrome).
| O que aparece | Significado |
|---|---|
lastCron é null ou de um dia anterior |
O cron não correu. Ver Settings, Cron Jobs, e os logs de /api/cron/notify |
subscriptions: 0 |
O servidor não tem dispositivos registados |
withStarred: 0 |
Os dispositivos não têm estrelas registadas no servidor |
withJobs: 0 |
Nenhum teste com estrela está nos próximos 7 dias |
errors maior que 0 |
O serviço de push recusou. errorStatuses indica o código; 401 e 403 apontam para as chaves VAPID ou para VAPID_SUBJECT |
sent maior que 0 |
O serviço de push aceitou o aviso. Se não apareceu, o problema está no dispositivo |
byService sem apple |
Nenhum iPhone recebeu envio nessa execução |
Para testar o envio automático em produção sem esperar pelas 10h (envia os avisos de hoje a todos os dispositivos):
curl -H "Authorization: Bearer $CRON_SECRET" "https://URL-DA-APP/api/cron/notify?force=1"| Ficheiro | O que faz |
|---|---|
lib/calendar-data.ts |
Lista de testes; editar aqui para mudar datas |
lib/subjects.ts |
Lista de UCs |
public/sw.js |
Service worker: cache e notificações |
app/actions.ts |
Subscrever e desubscrever |
lib/push/store.ts |
Dispositivos registados, entregas do dia e últimas execuções do cron |
lib/push/notify.ts |
Decide que avisos enviar |
lib/push/send.ts |
Envia uma notificação |
app/api/cron/notify/[[...slot]]/route.ts |
Endpoint chamado pelos crons |
app/api/push/health/route.ts |
Diagnóstico |
hooks/usePushNotifications.ts |
Lógica de push no browser |
hooks/useStarred.ts |
Testes com estrela |
hooks/useEventFilters.ts |
Filtros do calendário |
As notificações exigem iOS 16.4 ou mais recente e a app instalada no ecrã principal, aberta a partir do ícone. A subscrição feita no Safari não serve para a app instalada: os avisos têm de ser ligados dentro da app.
A partir do iOS 18.4, os avisos usam Declarative Web Push: o sistema mostra a notificação mesmo que o service worker falhe.