iGRPIGRP 3.0Documentation

SDK TypeScript

Cliente TypeScript do Notification Service (envio M2M, inbox, anexos, templates, erros) e editor visual de templates de email.

Dois pacotes npm para o frontend:

PacoteVersãoPara quê
@igrp/platform-notification-client-ts0.2.0-beta.4Cliente REST do Notification Service: envio, inbox, anexos, templates
@igrp/notification-template-editor0.1.0-beta.5Editor visual de templates de email (React)

Os pacotes estão no registry do iGRP:

# .npmrc
@igrp:registry=https://sonatype.nosi.cv/repository/igrp/

O passo a passo de uma app Next.js completa (factories, Server Actions, sino) está em Envio — SDKs e API. Esta página é a referência do SDK.


@igrp/platform-notification-client-ts

pnpm add @igrp/platform-notification-client-ts

NotificationClient agrupa um cliente por domínio da API, todos com a mesma configuração:

PropriedadeEndpointUso típico
notifications/api/v1/notificationssend, list, get_, resend
inbox/api/v1/inboxCaixa de entrada IN_APP do utilizador
attachments/api/v1/attachmentsupload / download de anexos
templates/api/v1/templateslist, create, update, publish, archive, preview
bulk, batchesEnvios em massa e o seu estado
recipients, groupsDestinatários e grupos guardados
recurrencesEnvios recorrentes
deliveries, statsEntregas por canal e estatísticas
settings, webhooks, adminConfiguração da aplicação e administração

Todos os métodos devolvem ApiResponse<T>: o corpo está em .data (e há status e statusText).

Autenticação

O serviço aceita dois tipos de token, e cada um serve para operações diferentes:

TokenPara quêComo criar o cliente
Utilizador (sessão)Inbox do próprio utilizador: o userId sai do tokenheaders.Authorization com o token da sessão
M2M (client_credentials)Enviar, anexos, templates, administraçãom2mConfig com clientId / clientSecret / authBaseUrl

Utilizador final

import { NotificationClient } from '@igrp/platform-notification-client-ts';
import { auth } from '@/lib/auth';

const token = await auth.getAccessToken(); // sessão do template iGRP

const client = NotificationClient.create({
  baseUrl: process.env.NOTIFICATION_SERVICE_BASE_URL!,
  timeout: 30000,
  headers: { Authorization: `Bearer ${token?.accessToken}` },
});

M2M

import { NotificationClient } from '@igrp/platform-notification-client-ts';

const client = NotificationClient.create(
  { baseUrl: process.env.NOTIFICATION_SERVICE_BASE_URL! },
  {
    clientId: process.env.IGRP_M2M_CLIENT_ID!,
    clientSecret: process.env.IGRP_M2M_CLIENT_SECRET!,
    authBaseUrl: process.env.IGRP_ACCESS_MANAGEMENT_API!, // Access Management, não o Notification Service
    serviceId: process.env.IGRP_SERVICE_ID, // opcional: header X-Machine-Service-ID
  },
);

O SDK pede o token em POST {authBaseUrl}/oauth2/token, guarda-o em cache e renova-o 30 s antes de expirar. scope é opcional: só o passes se o cliente OAuth2 o restringir.

authBaseUrl é o Access Management

Se authBaseUrl faltar, o SDK pede o token ao baseUrl do Notification Service, que responde 401. Aponta-o para a base do Access Management (ou do IdP).

Cria o cliente M2M uma vez

Cada cliente de domínio tem a sua cache de token. Guarda o NotificationClient num módulo (singleton) em vez de o criar a cada pedido. Se precisares do token em bruto, usa ClientCredentialsTokenProvider (exportado pelo pacote) e passa-o em headers.Authorization.

Todas as variáveis de ambiente são lidas no servidor (Server Actions, Route Handlers). O clientSecret nunca pode chegar ao bundle do browser.

applicationCode

Cada pedido pertence a uma aplicação do Access Management. O serviço lê-a do claim application_code do token e, se o token não a tiver, do applicationCode do pedido. Os tokens client_credentials normalmente não trazem esse claim (o erro é APPLICATION_CODE_REQUIRED), por isso passa sempre o código da app (IGRP_APP_CODE):

OperaçãoOnde vai
notifications.send(request)request.applicationCode
templates.list(filters, pageable, applicationCode)3.º argumento
attachments.upload(file, filename, applicationCode)3.º argumento
notifications.list / get_ / resend, templates.get_ / publish / archiveúltimo argumento

Enviar com template

EMAIL precisa de SMTP configurado

Para enviar por EMAIL, o provider SMTP tem de estar configurado no Notification Service (NOTIFICATION_PROVIDERS_EMAILSMTP_*: servidor, porta, credenciais e remetente). Sem ele, o envio é recusado com 422 CHANNEL_NOT_ENABLED e nenhum email sai — ver Configurar os canais › EMAIL (SMTP). O canal também tem de estar ativo nas Definições da aplicação.

'use server';

import { Channel, ChannelStrategy } from '@igrp/platform-notification-client-ts';
import { getNotificationM2MClient } from '@/lib/notification-client';

export async function enviarConfirmacao(input: { email: string; nome: string; referencia: string }) {
  const client = getNotificationM2MClient();
  const { data } = await client.notifications.send({
    applicationCode: process.env.IGRP_APP_CODE!,
    channelStrategy: ChannelStrategy.SPECIFIC,
    channels: [Channel.EMAIL],
    recipients: [{ email: input.email }],
    template: { code: 'pedido-confirmacao', locale: 'pt-CV' },
    variables: { nome: input.nome, numeroPedido: input.referencia },
    category: 'pedido-confirmacao',
    businessKey: input.referencia,
    idempotencyKey: `pedido-confirmacao-${input.referencia}`,
  });
  return data.id;
}

Campos de SendNotificationRequest:

CampoDescrição
channelStrategyObrigatório. SPECIFIC (só os channels indicados), ALL ou PREFERRED (preferências do destinatário).
channelsEMAIL, SMS, WHATSAPP, PUSH, IN_APP, OFFLINE.
recipients{ userId }, { email }, { phone } ou { pushToken } — endereços explícitos por canal.
groupsUm de: groupId (grupo guardado), departmentCode, roleCode ou permissionName do Access Management, resolvidos na hora do envio.
template{ code, locale? } — template publicado.
inline{ subject?, title?, body? } — conteúdo sem template. Usa template ou inline.
variablesValores para as variáveis Handlebars do template ({{nome}}).
priorityLOW, NORMAL, HIGH, URGENT.
attachments[{ filename, contentRef }] — ver Anexos.
category, businessKey, correlationIdAgrupamento e rastreio (o businessKey aparece na inbox).
idempotencyKeyRepetir o pedido com a mesma chave não duplica o envio.
locale, scheduledAt, expiresAt, callbackUrlOpcionais.

Template ou conteúdo inline

Não há endpoint para obter um template pelo código. Para saber se está publicado, lista com filtro:

import { Channel, TemplateStatus } from '@igrp/platform-notification-client-ts';

const { data } = await client.templates.list(
  { code: 'pedido-confirmacao', status: TemplateStatus.PUBLISHED, locale: 'pt-CV', channel: Channel.EMAIL },
  { page: 0, size: 5 },
  process.env.IGRP_APP_CODE,
);
const publicado = data.content?.[0];

Um padrão útil é enviar com o template e, se ele não existir ou falhar a renderização, repetir com conteúdo inline:

import { ApiClientError, IgrpNotificationErrorCode } from '@igrp/platform-notification-client-ts';

try {
  await client.notifications.send({ ...base, template: { code: 'pedido-confirmacao', locale: 'pt-CV' }, variables });
} catch (error) {
  const semTemplate =
    error instanceof ApiClientError &&
    (error.is(IgrpNotificationErrorCode.TEMPLATE_NOT_FOUND) || error.is(IgrpNotificationErrorCode.TEMPLATE_ERROR));
  if (!semTemplate) throw error;
  await client.notifications.send({
    ...base,
    inline: { subject: `Pedido ${referencia}`, title: 'Pedido recebido', body: `Olá ${nome}, recebemos o seu pedido.` },
  });
}

Anexos

O anexo é enviado primeiro e o envio refere-o pelo contentRef:

const pdf = new Blob([bytes], { type: 'application/pdf' }); // bytes: ArrayBuffer | Uint8Array

const { data: anexo } = await client.attachments.upload(pdf, 'recibo.pdf', process.env.IGRP_APP_CODE);

await client.notifications.send({
  ...base,
  attachments: [{ filename: 'recibo.pdf', contentRef: anexo.contentRef }],
});

upload recebe um Blob (ou File). Erros possíveis: ATTACHMENT_TOO_LARGE, ATTACHMENT_INVALID.


Inbox (IN_APP)

Com o cliente de utilizador:

MétodoEndpoint
inbox.list({ unreadOnly?, includeArchived? }, { page, size })GET /api/v1/inbox
inbox.unreadCount()GET /api/v1/inbox/unread-count
inbox.markRead(deliveryId)POST /api/v1/inbox/{deliveryId}/read
inbox.markAllRead()POST /api/v1/inbox/read
inbox.archive(deliveryId)POST /api/v1/inbox/{deliveryId}/archive

Cada item (InboxItemResponse) tem deliveryId, subject, title, body, category, businessKey, status, read, archived e createdAt. O body pode trazer HTML (templates de email): limpa-o antes de o mostrar como texto.

Sino da header

A header do framework mostra o que getHeaderData() devolver em notifications (IGRPNotificationArgs[], de @igrp/framework-next-types). Converte cada item da inbox:

// src/lib/map-inbox-notification.ts
import type { IGRPNotificationArgs } from '@igrp/framework-next-types';
import type { InboxItemResponse } from '@igrp/platform-notification-client-ts';

const texto = (html?: string) => (html ?? '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();

export function mapInboxItem(item: InboxItemResponse): IGRPNotificationArgs {
  return {
    id: item.deliveryId ?? '',
    title: texto(item.subject || item.title) || 'Notificação',
    message: texto(item.body) || item.businessKey || '',
    type: 'info',
    timestamp: item.createdAt ?? new Date().toISOString(),
    isRead: Boolean(item.read),
  };
}

A ligação ao getHeaderData() está em Envio — SDKs e API.


Erros

Os erros HTTP chegam como ApiClientError:

CampoConteúdo
statusCódigo HTTP
errorCodeCódigo do serviço (IgrpNotificationErrorCode), por exemplo TEMPLATE_NOT_FOUND, APPLICATION_CODE_REQUIRED, CHANNEL_NOT_ENABLED, QUOTA_EXCEEDED, VALIDATION_ERROR
serverMessageMensagem do serviço (envelope { code, message })
detailsCorpo da resposta
is(code)true se errorCode for esse código

Server Actions perdem os detalhes

Uma Server Action só passa message ao browser: errorCode, serverMessage e details perdem-se. Para mostrar o motivo num toast, apanha o ApiClientError no servidor e lança um Error cuja mensagem já inclua serverMessage e errorCode.


Variáveis de ambiente

VariávelUso
NOTIFICATION_SERVICE_BASE_URLBase do Notification Service, sem /api/v1 (no workspace do Studio: http://<slug>:<porta>/gateway-api/igrp-notification-service)
IGRP_APP_CODECódigo da aplicação → applicationCode
IGRP_M2M_CLIENT_ID, IGRP_M2M_CLIENT_SECRETCliente OAuth2 M2M — só servidor
IGRP_ACCESS_MANAGEMENT_API (ou IGRP_AUTH_ISSUER)authBaseUrl do token M2M
IGRP_SERVICE_IDOpcional — header X-Machine-Service-ID
NEXT_PUBLIC_IGRP_NOTIFICATION_URLPágina "ver todas" do sino (notificationsUrl)

@igrp/notification-template-editor

Editor visual React para compor templates de email por linhas e colunas. É o editor usado na Notifications UI. Precisa de React 19 e do Design System (@igrp/igrp-framework-react-design-system ≥ 0.2.0-beta.3).

pnpm add @igrp/notification-template-editor

Estilos, no CSS global (Tailwind v4):

@source "../../node_modules/@igrp/notification-template-editor/dist/**/*.{js,jsx,ts,tsx,mjs,cjs}";
@import "@igrp/notification-template-editor/styles";
'use client';

import { EmailTemplateRowEditor, type EmailTemplate } from '@igrp/notification-template-editor';

export function EditorTemplate({ onSave }: { onSave: (html: string) => void }) {
  return (
    <EmailTemplateRowEditor
      tagSyntax="handlebars"
      layout="fill"
      embedded
      persistLocal={false}
      onSave={(template: EmailTemplate, html: string) => onSave(html)}
    />
  );
}
PropDescrição
tagSyntax"handlebars" ({{nome}}) ou "go" (por omissão). O Notification Service renderiza Handlebars: usa "handlebars".
initialTemplate, initialDynamicTagsEstado inicial (EmailTemplate, DynamicTag[]).
onChange, onSaveRecebem (template, html): o HTML gerado vai para o corpo do template no serviço.
onDynamicTagsChangeAviso quando as variáveis disponíveis mudam.
embedded, layoutEmbutir numa página (layout="fill" ocupa a altura do contentor).
persistLocalGuardar o rascunho no localStorage do browser.
insertTokenRefRef para o host inserir uma variável no bloco selecionado.

Também exporta generateEmailHtml(template) (gera o HTML fora do editor) e os dados iniciais INITIAL_TEMPLATE, DEFAULT_DYNAMIC_TAGS e SAMPLE_PRESETS.

How is this guide?

Última atualização: