chore: remove dashboard nodes and credentials
This commit is contained in:
parent
54b7dd6ea1
commit
2a8fb1376d
8 changed files with 76 additions and 1333 deletions
238
README.md
238
README.md
|
|
@ -5,9 +5,7 @@ Node de comunidade para [n8n](https://n8n.io) para trabalhar com a API do Mega.
|
|||
## Recursos
|
||||
|
||||
- `Mega` node regular para APIs do Mega com escopo de conta
|
||||
- `Mega Client` node regular para APIs Client públicas
|
||||
- `Mega Dashboard App` node regular para gerar recursos de app embutido servido pelo n8n
|
||||
- `Mega Dashboard Script` node regular para gerar scripts de dashboard injetados por URL
|
||||
- `Mega Client` node regular para APIs Client públicas
|
||||
- `Mega Platform` node regular para APIs Platform do Mega
|
||||
- `Account -> Get` operation
|
||||
- `Account -> Update` operation
|
||||
|
|
@ -31,18 +29,16 @@ Node de comunidade para [n8n](https://n8n.io) para trabalhar com a API do Mega.
|
|||
- `Team -> Get Many`, `Get`, `Create`, `Update`, `Delete`, `Get Agents`, `Add Agent`, `Remove Agent`, and `Update Agents` operations
|
||||
- `Webhook -> Get Many`, `Create`, `Update`, and `Delete` operations
|
||||
- `Mega API` credential with `Base URL`, `API Access Token`, and `Mega Account ID`
|
||||
- `Mega Dashboard App` credential with `Base App URL`, `Shared Secret`, and `Allowed Mega Origin`
|
||||
- `Mega Dashboard Script API` credential with `Base Script URL`, `Default Iframe URL`, and `Allowed Mega Origin`
|
||||
- `Mega Client API` credential with `Base URL` and `Inbox Identifier`
|
||||
- `Mega Platform API` credential with `Base URL` and `Platform API Access Token`
|
||||
- Teste de conexão da credencial usando `GET /api/v1/profile`
|
||||
- Teste de conexão da credencial usando `GET /api/v1/profile`
|
||||
|
||||
## Requisitos
|
||||
|
||||
- Node.js 22+ é necessário para executar os comandos atuais de `build` e `lint` do `@n8n/node-cli`
|
||||
- Node.js 22+ é necessário para executar os comandos atuais de `build` e `lint` do `@n8n/node-cli`
|
||||
- npm
|
||||
|
||||
## Instalação
|
||||
## Instalação
|
||||
|
||||
```bash
|
||||
npm install
|
||||
|
|
@ -65,8 +61,8 @@ npm install @jessefreitas/n8n-nodes-mega
|
|||
|
||||
Crie uma credencial `Mega API` no n8n com:
|
||||
|
||||
- `Base URL`: URL da sua instância Mega, por exemplo `https://app.example.com`
|
||||
- `API Access Token`: token da aplicação enviado no header `api_access_token`
|
||||
- `Base URL`: URL da sua instância Mega, por exemplo `https://app.example.com`
|
||||
- `API Access Token`: token da aplicação enviado no header `api_access_token`
|
||||
- `Mega Account ID`: identificador externo da conta Mega usado em endpoints com escopo de conta
|
||||
|
||||
O teste da credencial chama `GET /api/v1/profile` para validar o token.
|
||||
|
|
@ -81,7 +77,7 @@ Use o node `Mega Platform` com a credencial `Mega Platform API` para endpoints d
|
|||
|
||||
Crie uma credencial `Mega Platform API` no n8n com:
|
||||
|
||||
- `Base URL`: URL da sua instância Mega
|
||||
- `Base URL`: URL da sua instância Mega
|
||||
- `Platform API Access Token`: token do app platform enviado no header `api_access_token`
|
||||
|
||||
O teste da credencial chama:
|
||||
|
|
@ -99,13 +95,13 @@ Recursos suportados em `Mega Platform`:
|
|||
|
||||
Importante:
|
||||
|
||||
- `Mega` and `Mega Platform` não compartilham credenciais
|
||||
- `Mega` é para APIs de aplicação com escopo de conta em `/api/v1/accounts/*`
|
||||
- `Mega Platform` é para APIs Platform em `/platform/api/v1/*`
|
||||
- `Mega` and `Mega Platform` não compartilham credenciais
|
||||
- `Mega` é para APIs de aplicação com escopo de conta em `/api/v1/accounts/*`
|
||||
- `Mega Platform` é para APIs Platform em `/platform/api/v1/*`
|
||||
|
||||
## API Client
|
||||
|
||||
Use o node `Mega Client` com a credencial `Mega Client API` para endpoints públicos client em:
|
||||
Use o node `Mega Client` com a credencial `Mega Client API` para endpoints públicos client em:
|
||||
|
||||
```text
|
||||
/public/api/v1/*
|
||||
|
|
@ -113,8 +109,8 @@ Use o node `Mega Client` com a credencial `Mega Client API` para endpoints públ
|
|||
|
||||
Crie uma credencial `Mega Client API` no n8n com:
|
||||
|
||||
- `Base URL`: URL da sua instância Mega
|
||||
- `Inbox Identifier`: identificador público da caixa de entrada usado pelas APIs Client
|
||||
- `Base URL`: URL da sua instância Mega
|
||||
- `Inbox Identifier`: identificador público da caixa de entrada usado pelas APIs Client
|
||||
|
||||
Recursos suportados em `Mega Client`:
|
||||
|
||||
|
|
@ -125,63 +121,16 @@ Recursos suportados em `Mega Client`:
|
|||
|
||||
Importante:
|
||||
|
||||
- `Mega`, `Mega Platform`, and `Mega Client` não compartilham credenciais
|
||||
- `Mega Client` usa identificadores públicos como `inbox_identifier`, `contact_identifier`, and `conversation_id`
|
||||
- `CSAT Survey` usa uma rota pública `conversation_uuid` fora do padrão `/public/api/v1/inboxes/*`
|
||||
- `Mega`, `Mega Platform`, and `Mega Client` não compartilham credenciais
|
||||
- `Mega Client` usa identificadores públicos como `inbox_identifier`, `contact_identifier`, and `conversation_id`
|
||||
- `CSAT Survey` usa uma rota pública `conversation_uuid` fora do padrão `/public/api/v1/inboxes/*`
|
||||
|
||||
## Mega Dashboard App
|
||||
|
||||
Use o node `Mega Dashboard App` com a credencial `Mega Dashboard App` para gerar os recursos necessários para um app embutido dentro do dashboard do Mega.
|
||||
|
||||
Crie uma credencial `Mega Dashboard App` no n8n com:
|
||||
|
||||
- `Base App URL`: URL pública que servirá o app embutido
|
||||
- `Shared Secret`: segredo usado pelo app embutido ao chamar o n8n de volta
|
||||
- `Allowed Mega Origin`: origem esperada do Mega permitida para enviar contexto via `postMessage`
|
||||
- `App Name`: nome padrão opcional do app para a configuração gerada
|
||||
- `App Icon URL`: URL opcional do ícone para a configuração gerada
|
||||
|
||||
Operações suportadas em `Mega Dashboard App`:
|
||||
|
||||
- `Generate Config`
|
||||
- `Generate Context Bridge`
|
||||
- `Generate Embed Page`
|
||||
- `Verify Request`
|
||||
|
||||
Importante:
|
||||
|
||||
- `Mega Dashboard App` não grava configurações no Mega automaticamente
|
||||
- o app é registrado manualmente no Mega usando a configuração gerada
|
||||
- o app embutido deve ser servido por um webhook do n8n ou outra URL pública sob seu controle
|
||||
- injeção de script de dashboard está fora do escopo deste node
|
||||
|
||||
## Mega Dashboard Script
|
||||
|
||||
Use o node `Mega Dashboard Script` com a credencial `Mega Dashboard Script API` para gerar o JavaScript que o Mega carrega por URL na área de Dashboard Scripts.
|
||||
|
||||
Crie uma credencial `Mega Dashboard Script API` no n8n com:
|
||||
|
||||
- `Base Script URL`: URL pública que vai servir o JavaScript injetado
|
||||
- `Default Iframe URL`: URL padrão aberta dentro do painel embutido
|
||||
- `Allowed Mega Origin`: origem esperada do Mega que pode executar o script
|
||||
|
||||
Operações suportadas em `Mega Dashboard Script`:
|
||||
|
||||
- `Generate Config`
|
||||
- `Generate Script`
|
||||
|
||||
Importante:
|
||||
|
||||
- este node é para o caso em que o Mega espera um link de script para injeção
|
||||
- o script gerado adiciona um item na sidebar e abre um iframe dentro do painel do Mega
|
||||
- o script tenta manter a sidebar visível, acompanha o tema claro/escuro e pode fechar o painel ao clicar em outro item da sidebar
|
||||
- o JavaScript deve ser servido com `content-type: application/javascript`
|
||||
|
||||
## Operações
|
||||
## Operações
|
||||
|
||||
### Account -> Get
|
||||
|
||||
Obtém detalhes da conta em:
|
||||
Obtém detalhes da conta em:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}
|
||||
|
|
@ -202,60 +151,25 @@ Suporta atualizar estes campos:
|
|||
- `Company Size`
|
||||
- `Timezone`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
PATCH /api/v1/accounts/{accountId}
|
||||
```
|
||||
|
||||
## Mega Dashboard App Operations
|
||||
|
||||
O node `Mega Dashboard App` ajuda a preparar um app de dashboard servido pelo n8n e registrado manualmente no Mega.
|
||||
|
||||
Saídas geradas:
|
||||
|
||||
- `Generate Config`: returns app metadata, iframe URL, allowed origin, shared secret, and manual setup steps
|
||||
- `Generate Context Bridge`: returns the browser JavaScript that receives Mega context via `postMessage` and forwards actions to an n8n webhook
|
||||
- `Generate Embed Page`: returns HTML ready for an `HTTP Response` node or another public endpoint
|
||||
- `Verify Request`: validates request origin and shared secret against the credential
|
||||
|
||||
Fluxo recomendado:
|
||||
|
||||
1. Crie um webhook público no n8n que servirá a página embutida.
|
||||
2. Use `Generate Embed Page` para produzir o HTML retornado por esse webhook.
|
||||
3. Use `Generate Config` para coletar a URL do iframe e os valores de registro no Mega.
|
||||
4. Registre o app manualmente no Mega.
|
||||
5. Use `Verify Request` no fluxo de backend antes de processar ações sensíveis.
|
||||
|
||||
## Mega Dashboard Script Operations
|
||||
|
||||
O node `Mega Dashboard Script` ajuda a gerar um script injetável por URL para a área de Dashboard Scripts do Mega.
|
||||
|
||||
Saídas geradas:
|
||||
|
||||
- `Generate Config`: retorna a URL pública do script, um loader inline opcional, a configuração final do script e os passos manuais
|
||||
- `Generate Script`: retorna o JavaScript completo com `content_type` igual a `application/javascript; charset=utf-8`
|
||||
|
||||
Fluxo recomendado:
|
||||
|
||||
1. Crie um webhook público no n8n que vai responder com o JavaScript do script.
|
||||
2. Use `Generate Script` para obter o JavaScript e devolva esse conteúdo no webhook com `application/javascript`.
|
||||
3. Use `Generate Config` para obter a URL do script e o loader inline opcional.
|
||||
4. No Mega, configure o link do Dashboard Script apontando para a URL pública do script ou cole o loader inline, se necessário.
|
||||
5. Recarregue o dashboard do Mega e valide se o botão aparece na posição escolhida.
|
||||
|
||||
## Mega Platform Operations
|
||||
|
||||
### Platform Account
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Create`
|
||||
- `Get`
|
||||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
POST /platform/api/v1/accounts
|
||||
|
|
@ -276,13 +190,13 @@ Campos suportados:
|
|||
|
||||
### Platform Account User
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /platform/api/v1/accounts/{accountId}/account_users
|
||||
|
|
@ -298,7 +212,7 @@ Campos suportados:
|
|||
|
||||
### Platform Agent Bot
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -306,7 +220,7 @@ Operações suportadas:
|
|||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /platform/api/v1/agent_bots
|
||||
|
|
@ -328,7 +242,7 @@ The documented binary `avatar` upload field is not implemented yet in this node.
|
|||
|
||||
### Platform User
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Create`
|
||||
- `Get`
|
||||
|
|
@ -336,7 +250,7 @@ Operações suportadas:
|
|||
- `Delete`
|
||||
- `Get SSO Link`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
POST /platform/api/v1/users
|
||||
|
|
@ -358,13 +272,13 @@ Campos suportados:
|
|||
|
||||
### Client Contact
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Create`
|
||||
- `Get`
|
||||
- `Update`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
POST /public/api/v1/inboxes/{inboxIdentifier}/contacts
|
||||
|
|
@ -386,7 +300,7 @@ The documented binary `avatar` upload field is not implemented yet in this node.
|
|||
|
||||
### Client Conversation
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -395,7 +309,7 @@ Operações suportadas:
|
|||
- `Toggle Typing Status`
|
||||
- `Update Last Seen`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /public/api/v1/inboxes/{inboxIdentifier}/contacts/{contactIdentifier}/conversations
|
||||
|
|
@ -415,13 +329,13 @@ Campos suportados:
|
|||
|
||||
### Client Message
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Update`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /public/api/v1/inboxes/{inboxIdentifier}/contacts/{contactIdentifier}/conversations/{conversationId}/messages
|
||||
|
|
@ -440,11 +354,11 @@ Campos suportados:
|
|||
|
||||
### Client CSAT Survey
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /survey/responses/{conversationUuid}
|
||||
|
|
@ -466,11 +380,11 @@ Available fields:
|
|||
|
||||
- `Page`
|
||||
|
||||
Este endpoint só está disponível em edições Enterprise do Mega com o recurso de logs de auditoria habilitado.
|
||||
Este endpoint só está disponÃvel em edições Enterprise do Mega com o recurso de logs de auditoria habilitado.
|
||||
|
||||
### Agent Bot
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -478,7 +392,7 @@ Operações suportadas:
|
|||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/agent_bots
|
||||
|
|
@ -501,14 +415,14 @@ The documented binary `avatar` upload field is not implemented yet in this node.
|
|||
|
||||
### Agent
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/agents
|
||||
|
|
@ -527,7 +441,7 @@ Campos suportados:
|
|||
|
||||
### Automation Rule
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -535,7 +449,7 @@ Operações suportadas:
|
|||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/automation_rules
|
||||
|
|
@ -556,11 +470,11 @@ Campos suportados:
|
|||
|
||||
### Campaign
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Create`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
POST /api/v1/accounts/{accountId}/campaigns
|
||||
|
|
@ -580,14 +494,14 @@ This first implementation covers only campaign creation and models `audience` as
|
|||
|
||||
### Canned Response
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/canned_responses
|
||||
|
|
@ -603,7 +517,7 @@ Campos suportados:
|
|||
|
||||
### Chat Room
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -618,7 +532,7 @@ Operações suportadas:
|
|||
- `Get Messages`
|
||||
- `Create Message`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/chat_rooms
|
||||
|
|
@ -652,7 +566,7 @@ This first implementation uses JSON payloads only. Binary avatar uploads for roo
|
|||
|
||||
### Custom Filter
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -660,7 +574,7 @@ Operações suportadas:
|
|||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/custom_filters
|
||||
|
|
@ -679,7 +593,7 @@ Campos suportados:
|
|||
|
||||
### Contact
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -699,7 +613,7 @@ Operações suportadas:
|
|||
- `Set Labels`
|
||||
- `Merge`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/contacts
|
||||
|
|
@ -745,13 +659,13 @@ The documented binary `avatar` upload field is not implemented yet in this node.
|
|||
|
||||
### Portal
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Update`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/portals
|
||||
|
|
@ -773,11 +687,11 @@ Campos suportados:
|
|||
|
||||
### Portal Category
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Create`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
POST /api/v1/accounts/{accountId}/portals/{id}/categories
|
||||
|
|
@ -796,11 +710,11 @@ Campos suportados:
|
|||
|
||||
### Portal Article
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Create`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
POST /api/v1/accounts/{accountId}/portals/{id}/articles
|
||||
|
|
@ -822,7 +736,7 @@ Campos suportados:
|
|||
|
||||
### Custom Attribute
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
|
|
@ -830,7 +744,7 @@ Operações suportadas:
|
|||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/custom_attribute_definitions
|
||||
|
|
@ -854,7 +768,7 @@ Campos suportados:
|
|||
|
||||
### Inbox
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Get`
|
||||
|
|
@ -867,7 +781,7 @@ Operações suportadas:
|
|||
- `Remove Agent`
|
||||
- `Update Agents`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/inboxes
|
||||
|
|
@ -907,14 +821,14 @@ The documented binary `avatar` upload field is not implemented yet in this node.
|
|||
|
||||
### Integration
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/integrations/hooks
|
||||
|
|
@ -932,13 +846,13 @@ Campos suportados:
|
|||
|
||||
### Message
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/conversations/{conversationId}/messages
|
||||
|
|
@ -961,7 +875,7 @@ Campos suportados:
|
|||
|
||||
### Scheduled Message
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Get`
|
||||
|
|
@ -975,7 +889,7 @@ The node supports two scopes:
|
|||
- `Account`
|
||||
- `Conversation`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/scheduled_messages
|
||||
|
|
@ -1022,7 +936,7 @@ GET /api/v1/profile
|
|||
|
||||
### Team
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Get`
|
||||
|
|
@ -1034,7 +948,7 @@ Operações suportadas:
|
|||
- `Remove Agent`
|
||||
- `Update Agents`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/teams
|
||||
|
|
@ -1057,14 +971,14 @@ Campos suportados:
|
|||
|
||||
### Webhook
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Many`
|
||||
- `Create`
|
||||
- `Update`
|
||||
- `Delete`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/webhooks
|
||||
|
|
@ -1081,7 +995,7 @@ Campos suportados:
|
|||
|
||||
### Conversation
|
||||
|
||||
Operações suportadas:
|
||||
Operações suportadas:
|
||||
|
||||
- `Get Counts`
|
||||
- `Get Many`
|
||||
|
|
@ -1100,7 +1014,7 @@ Operações suportadas:
|
|||
- `Get Reporting Events`
|
||||
- `Assign`
|
||||
|
||||
O node envia requisições para:
|
||||
O node envia requisições para:
|
||||
|
||||
```text
|
||||
GET /api/v1/accounts/{accountId}/conversations/meta
|
||||
|
|
@ -1145,11 +1059,11 @@ Campos suportados:
|
|||
- `Typing Status`
|
||||
- `Private Note`
|
||||
|
||||
## Validação local
|
||||
## Validação local
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
|
||||
Se você estiver usando uma versão mais antiga do Node.js, o `@n8n/node-cli` atual pode falhar durante a validação local. Use Node.js 22+ antes de publicar ou submeter o pacote para revisão.
|
||||
Se você estiver usando uma versão mais antiga do Node.js, o `@n8n/node-cli` atual pode falhar durante a validação local. Use Node.js 22+ antes de publicar ou submeter o pacote para revisão.
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import type { Icon, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export class MegaDashboardAppApi implements ICredentialType {
|
||||
name = 'megaDashboardAppApi';
|
||||
|
||||
displayName = 'Mega Dashboard App API';
|
||||
|
||||
documentationUrl = 'https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file#mega-dashboard-app';
|
||||
|
||||
icon: Icon = { light: 'file:../icons/mega.svg', dark: 'file:../icons/mega.dark.svg' };
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'URL Base do App',
|
||||
name: 'baseAppUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://n8n.example.com/webhook/mega-dashboard-app',
|
||||
required: true,
|
||||
description: 'URL pública que servirá o app embutido do dashboard',
|
||||
},
|
||||
{
|
||||
displayName: 'Segredo Compartilhado',
|
||||
name: 'sharedSecret',
|
||||
type: 'string',
|
||||
typeOptions: { password: true },
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Segredo compartilhado usado entre o app embutido e os webhooks do n8n',
|
||||
},
|
||||
{
|
||||
displayName: 'Origem Mega Permitida',
|
||||
name: 'allowedMegaOrigin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://chat.example.com',
|
||||
required: true,
|
||||
description: 'Origem do Mega permitida para enviar contexto do dashboard via postMessage',
|
||||
},
|
||||
{
|
||||
displayName: 'Nome do App',
|
||||
name: 'appName',
|
||||
type: 'string',
|
||||
default: 'Mega Dashboard App',
|
||||
description: 'Nome padrão exibido nas saídas de configuração geradas',
|
||||
},
|
||||
{
|
||||
displayName: 'URL do Ícone do App',
|
||||
name: 'appIconUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL opcional do ícone usada nas saídas de configuração geradas',
|
||||
},
|
||||
];
|
||||
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
url: '={{$credentials.baseAppUrl}}',
|
||||
method: 'GET',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import type { Icon, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export class MegaDashboardScriptApi implements ICredentialType {
|
||||
name = 'megaDashboardScriptApi';
|
||||
|
||||
displayName = 'Mega Dashboard Script API';
|
||||
|
||||
documentationUrl =
|
||||
'https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file#mega-dashboard-script';
|
||||
|
||||
icon: Icon = { light: 'file:../icons/mega.svg', dark: 'file:../icons/mega.dark.svg' };
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Base Script URL',
|
||||
name: 'baseScriptUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://n8n.example.com/webhook/mega-dashboard-script',
|
||||
required: true,
|
||||
description: 'Public URL that will serve the injected dashboard script',
|
||||
},
|
||||
{
|
||||
displayName: 'Default Iframe URL',
|
||||
name: 'defaultIframeUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://n8n.example.com/webhook/mega-dashboard-app',
|
||||
required: true,
|
||||
description: 'Default URL opened inside the embedded dashboard panel',
|
||||
},
|
||||
{
|
||||
displayName: 'Allowed Mega Origin',
|
||||
name: 'allowedMegaOrigin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://chat.example.com',
|
||||
required: true,
|
||||
description: 'Mega origin allowed to execute the injected dashboard script',
|
||||
},
|
||||
];
|
||||
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
url: '={{$credentials.baseScriptUrl}}',
|
||||
method: 'GET',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"node": "n8n-nodes-mega.megaDashboardApp",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file#mega-dashboard-app"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,530 +0,0 @@
|
|||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
function normalizeOrigin(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
const match = trimmed.match(/^(https?:\/\/[^/]+)/i);
|
||||
return (match?.[1] || trimmed).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function buildContextBridgeScript(
|
||||
allowedMegaOrigin: string,
|
||||
backendWebhookUrl: string,
|
||||
sharedSecret: string,
|
||||
): string {
|
||||
return `(() => {
|
||||
const CFG = {
|
||||
allowedMegaOrigin: ${JSON.stringify(allowedMegaOrigin)},
|
||||
backendWebhookUrl: ${JSON.stringify(backendWebhookUrl)},
|
||||
sharedSecret: ${JSON.stringify(sharedSecret)},
|
||||
};
|
||||
const state = { context: null };
|
||||
const normalizeOrigin = (value) => {
|
||||
try {
|
||||
return new URL(value).origin;
|
||||
} catch {
|
||||
return String(value || '').trim().replace(/\\/+$/, '');
|
||||
}
|
||||
};
|
||||
const emitContext = () => {
|
||||
window.dispatchEvent(new CustomEvent('mega-dashboard-context', { detail: state.context }));
|
||||
};
|
||||
window.megaDashboardApp = {
|
||||
getContext() {
|
||||
return state.context;
|
||||
},
|
||||
async send(payload = {}) {
|
||||
const response = await fetch(CFG.backendWebhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Mega-Shared-Secret': CFG.sharedSecret,
|
||||
'X-Mega-Source': 'dashboard-app',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
context: state.context,
|
||||
payload,
|
||||
}),
|
||||
});
|
||||
const text = await response.text();
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
raw: text,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
window.addEventListener('message', (event) => {
|
||||
if (
|
||||
CFG.allowedMegaOrigin &&
|
||||
normalizeOrigin(event.origin) !== normalizeOrigin(CFG.allowedMegaOrigin)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
state.context = event.data || null;
|
||||
emitContext();
|
||||
});
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: 'mega-dashboard-app:ready' }, '*');
|
||||
}
|
||||
})();`;
|
||||
}
|
||||
|
||||
function buildEmbedPageHtml(
|
||||
pageTitle: string,
|
||||
pageHeading: string,
|
||||
loadingText: string,
|
||||
emptyStateText: string,
|
||||
bridgeScript: string,
|
||||
): string {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${escapeHtml(pageTitle)}</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--bg: #f4f1ea;
|
||||
--panel: #fffdf9;
|
||||
--text: #18222f;
|
||||
--muted: #5f6b78;
|
||||
--border: #d8d2c7;
|
||||
--accent: #0f766e;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #10161d;
|
||||
--panel: #16212b;
|
||||
--text: #edf3f8;
|
||||
--muted: #9fb0bf;
|
||||
--border: #293746;
|
||||
--accent: #4fd1c5;
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
background: linear-gradient(180deg, var(--bg), var(--panel));
|
||||
color: var(--text);
|
||||
}
|
||||
.wrap {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: color-mix(in srgb, var(--panel) 92%, transparent);
|
||||
padding: 18px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 28px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
color: var(--accent);
|
||||
font: 600 12px/1.2 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="card">
|
||||
<span class="pill">Mega Dashboard App</span>
|
||||
<h1>${escapeHtml(pageHeading)}</h1>
|
||||
<p id="mega-dashboard-status">${escapeHtml(loadingText)}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<pre id="mega-dashboard-context">${escapeHtml(emptyStateText)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<script>${bridgeScript}</script>
|
||||
<script>
|
||||
(() => {
|
||||
const status = document.getElementById('mega-dashboard-status');
|
||||
const contextPre = document.getElementById('mega-dashboard-context');
|
||||
const emptyState = ${JSON.stringify(emptyStateText)};
|
||||
const render = () => {
|
||||
const context = window.megaDashboardApp?.getContext?.();
|
||||
if (!context) {
|
||||
status.textContent = ${JSON.stringify(loadingText)};
|
||||
contextPre.textContent = emptyState;
|
||||
return;
|
||||
}
|
||||
status.textContent = 'Context received from Mega';
|
||||
contextPre.textContent = JSON.stringify(context, null, 2);
|
||||
};
|
||||
window.addEventListener('mega-dashboard-context', render);
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
export class MegaDashboardApp implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Mega Dashboard App',
|
||||
name: 'megaDashboardApp',
|
||||
icon: { light: 'file:../../icons/mega.svg', dark: 'file:../../icons/mega.dark.svg' },
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"]}}',
|
||||
description: 'Generate and verify assets for a Mega dashboard app served by n8n',
|
||||
defaults: {
|
||||
name: 'Mega Dashboard App',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
usableAsTool: true,
|
||||
credentials: [
|
||||
{
|
||||
name: 'megaDashboardAppApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Generate Config',
|
||||
value: 'generateConfig',
|
||||
description: 'Generate config for manual mega registration',
|
||||
action: 'Generate config for manual mega registration',
|
||||
},
|
||||
{
|
||||
name: 'Generate Context Bridge',
|
||||
value: 'generateContextBridge',
|
||||
description: 'Generate context bridge script for the embedded app',
|
||||
action: 'Generate context bridge script for the embedded app',
|
||||
},
|
||||
{
|
||||
name: 'Generate Embed Page',
|
||||
value: 'generateEmbedPage',
|
||||
description: 'Generate html page for an n8n webhook response',
|
||||
action: 'Generate html page for an n8n webhook response',
|
||||
},
|
||||
{
|
||||
name: 'Verify Request',
|
||||
value: 'verifyRequest',
|
||||
description: 'Verify request origin and shared secret',
|
||||
action: 'Verify request origin and shared secret',
|
||||
},
|
||||
],
|
||||
default: 'generateConfig',
|
||||
},
|
||||
{
|
||||
displayName: 'App Name',
|
||||
name: 'dashboardAppName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optional name override for the generated dashboard app config',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateConfig'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Sidebar Label',
|
||||
name: 'dashboardSidebarLabel',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optional sidebar label shown in the Mega dashboard',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateConfig'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'App Description',
|
||||
name: 'dashboardAppDescription',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 3,
|
||||
},
|
||||
default: '',
|
||||
description: 'Optional description used in generated app config output',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateConfig'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Iframe URL',
|
||||
name: 'dashboardIframeUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optional iframe URL override. Leave empty to use the credential Base App URL.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateConfig'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Backend Webhook URL',
|
||||
name: 'dashboardBackendWebhookUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Webhook URL the embedded app should call back to in n8n',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateContextBridge', 'generateEmbedPage'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Page Title',
|
||||
name: 'dashboardPageTitle',
|
||||
type: 'string',
|
||||
default: 'Mega Dashboard App',
|
||||
description: 'Title used in the generated HTML page',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateEmbedPage'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Page Heading',
|
||||
name: 'dashboardPageHeading',
|
||||
type: 'string',
|
||||
default: 'Mega Dashboard App',
|
||||
description: 'Heading shown in the generated HTML page',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateEmbedPage'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Loading Text',
|
||||
name: 'dashboardLoadingText',
|
||||
type: 'string',
|
||||
default: 'Waiting for context from Mega...',
|
||||
description: 'Loading message shown before dashboard context is received',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateEmbedPage'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Empty State Text',
|
||||
name: 'dashboardEmptyStateText',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 3,
|
||||
},
|
||||
default: 'No dashboard context received yet.',
|
||||
description: 'Fallback text shown until context arrives',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['generateEmbedPage'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Request Origin',
|
||||
name: 'verifyRequestOrigin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Origin of the incoming request or postMessage event',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['verifyRequest'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Provided Secret',
|
||||
name: 'verifyProvidedSecret',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Shared secret received from the embedded app request',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['verifyRequest'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const credentials = await this.getCredentials('megaDashboardAppApi');
|
||||
const baseAppUrl = credentials.baseAppUrl as string;
|
||||
const sharedSecret = credentials.sharedSecret as string;
|
||||
const allowedMegaOrigin = normalizeOrigin(credentials.allowedMegaOrigin as string);
|
||||
const defaultAppName = ((credentials.appName as string) || 'Mega Dashboard App').trim();
|
||||
const defaultAppIconUrl = ((credentials.appIconUrl as string) || '').trim();
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const operation = this.getNodeParameter('operation', itemIndex) as string;
|
||||
let response: IDataObject;
|
||||
|
||||
if (operation === 'generateConfig') {
|
||||
const appName = (
|
||||
(this.getNodeParameter('dashboardAppName', itemIndex, '') as string) || defaultAppName
|
||||
).trim();
|
||||
const sidebarLabel = (
|
||||
(this.getNodeParameter('dashboardSidebarLabel', itemIndex, '') as string) || appName
|
||||
).trim();
|
||||
const appDescription = this.getNodeParameter(
|
||||
'dashboardAppDescription',
|
||||
itemIndex,
|
||||
'',
|
||||
) as string;
|
||||
const iframeUrl = (
|
||||
(this.getNodeParameter('dashboardIframeUrl', itemIndex, '') as string) || baseAppUrl
|
||||
).trim();
|
||||
|
||||
response = {
|
||||
app_name: appName,
|
||||
sidebar_label: sidebarLabel,
|
||||
iframe_url: iframeUrl,
|
||||
allowed_mega_origin: allowedMegaOrigin,
|
||||
shared_secret: sharedSecret,
|
||||
app_icon_url: defaultAppIconUrl || undefined,
|
||||
description: appDescription || undefined,
|
||||
manual_setup_steps: [
|
||||
'Open the Mega dashboard app configuration screen.',
|
||||
'Create a new dashboard app entry.',
|
||||
'Paste the generated iframe URL and sidebar label.',
|
||||
'Restrict the app to the allowed Mega origin.',
|
||||
'Use the shared secret in n8n webhook verification.',
|
||||
],
|
||||
};
|
||||
} else if (operation === 'generateContextBridge') {
|
||||
const backendWebhookUrl = this.getNodeParameter(
|
||||
'dashboardBackendWebhookUrl',
|
||||
itemIndex,
|
||||
) as string;
|
||||
|
||||
response = {
|
||||
allowed_mega_origin: allowedMegaOrigin,
|
||||
backend_webhook_url: backendWebhookUrl,
|
||||
javascript: buildContextBridgeScript(
|
||||
allowedMegaOrigin,
|
||||
backendWebhookUrl,
|
||||
sharedSecret,
|
||||
),
|
||||
};
|
||||
} else if (operation === 'generateEmbedPage') {
|
||||
const backendWebhookUrl = this.getNodeParameter(
|
||||
'dashboardBackendWebhookUrl',
|
||||
itemIndex,
|
||||
) as string;
|
||||
const pageTitle = this.getNodeParameter('dashboardPageTitle', itemIndex) as string;
|
||||
const pageHeading = this.getNodeParameter('dashboardPageHeading', itemIndex) as string;
|
||||
const loadingText = this.getNodeParameter('dashboardLoadingText', itemIndex) as string;
|
||||
const emptyStateText = this.getNodeParameter(
|
||||
'dashboardEmptyStateText',
|
||||
itemIndex,
|
||||
) as string;
|
||||
const bridgeScript = buildContextBridgeScript(
|
||||
allowedMegaOrigin,
|
||||
backendWebhookUrl,
|
||||
sharedSecret,
|
||||
);
|
||||
|
||||
response = {
|
||||
content_type: 'text/html; charset=utf-8',
|
||||
html: buildEmbedPageHtml(
|
||||
pageTitle,
|
||||
pageHeading,
|
||||
loadingText,
|
||||
emptyStateText,
|
||||
bridgeScript,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
const requestOrigin = normalizeOrigin(
|
||||
this.getNodeParameter('verifyRequestOrigin', itemIndex) as string,
|
||||
);
|
||||
const providedSecret = this.getNodeParameter(
|
||||
'verifyProvidedSecret',
|
||||
itemIndex,
|
||||
) as string;
|
||||
const originValid = requestOrigin === allowedMegaOrigin;
|
||||
const secretValid = providedSecret === sharedSecret;
|
||||
|
||||
response = {
|
||||
valid: originValid && secretValid,
|
||||
origin_valid: originValid,
|
||||
secret_valid: secretValid,
|
||||
expected_origin: allowedMegaOrigin,
|
||||
received_origin: requestOrigin,
|
||||
issues: [
|
||||
...(originValid ? [] : ['Origin does not match the allowed Mega origin']),
|
||||
...(secretValid ? [] : ['Shared secret does not match']),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
json: response,
|
||||
pairedItem: itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"node": "n8n-nodes-mega.megaDashboardScript",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file#mega-dashboard-script"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,490 +0,0 @@
|
|||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
function normalizeOrigin(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
const match = trimmed.match(/^(https?:\/\/[^/]+)/i);
|
||||
return (match?.[1] || trimmed).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function buildDashboardScript(config: {
|
||||
allowedMegaOrigin: string;
|
||||
autoHideOnNavigation: boolean;
|
||||
iframeUrl: string;
|
||||
linkIcon: string;
|
||||
linkText: string;
|
||||
padding: number;
|
||||
positionIndex: number;
|
||||
positionLabel: string;
|
||||
positionMode: string;
|
||||
showBorder: boolean;
|
||||
}): string {
|
||||
return `(() => {
|
||||
const CFG = ${JSON.stringify(config, null, 2)};
|
||||
|
||||
const $ = (selector, root = document) => {
|
||||
try {
|
||||
return root.querySelector(selector);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const $$ = (selector, root = document) => {
|
||||
try {
|
||||
return Array.from(root.querySelectorAll(selector));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const normalize = (value) => String(value || '').toLowerCase().replace(/\\s+/g, ' ').trim();
|
||||
|
||||
const normalizeOrigin = (value) => {
|
||||
try {
|
||||
return new URL(value).origin.replace(/\\/+$/, '');
|
||||
} catch {
|
||||
return String(value || '').trim().replace(/\\/+$/, '');
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
CFG.allowedMegaOrigin &&
|
||||
normalizeOrigin(window.location.origin) !== normalizeOrigin(CFG.allowedMegaOrigin)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
function findSidebar() {
|
||||
return $('[data-testid="sidebar-primary"]') || $('aside');
|
||||
}
|
||||
|
||||
function findAnyList() {
|
||||
const sidebar = findSidebar();
|
||||
if (!sidebar) return null;
|
||||
return $('ul', sidebar);
|
||||
}
|
||||
|
||||
function findListAndReferenceByLabel(labelWanted) {
|
||||
const sidebar = findSidebar();
|
||||
if (!sidebar) return { list: null, ref: null };
|
||||
|
||||
const wanted = normalize(labelWanted);
|
||||
const lists = $$('ul', sidebar);
|
||||
|
||||
for (const list of lists) {
|
||||
const items = $$(':scope > li', list);
|
||||
for (const item of items) {
|
||||
const text = normalize(item.textContent);
|
||||
if (text === wanted || text.includes(wanted)) {
|
||||
return { list, ref: item };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { list: null, ref: null };
|
||||
}
|
||||
|
||||
function setLinkActive(active) {
|
||||
const link = $('#mega-dashboard-script-link-anchor');
|
||||
if (!link) return;
|
||||
link.style.background = active ? 'rgba(15, 118, 110, 0.12)' : 'transparent';
|
||||
}
|
||||
|
||||
function ensurePanel() {
|
||||
if ($('#mega-dashboard-script-panel')) return $('#mega-dashboard-script-panel');
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'mega-dashboard-script-panel';
|
||||
panel.style.cssText =
|
||||
'position:fixed;inset:auto 0 0 0;top:0;z-index:9;display:none;pointer-events:none;';
|
||||
|
||||
const frameWrap = document.createElement('div');
|
||||
frameWrap.id = 'mega-dashboard-script-frame-wrap';
|
||||
frameWrap.style.cssText =
|
||||
'height:100%;width:100%;padding:0;pointer-events:auto;background:transparent;';
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'mega-dashboard-script-iframe';
|
||||
iframe.setAttribute('title', CFG.linkText || 'Mega Dashboard Script');
|
||||
iframe.style.cssText = 'width:100%;height:100%;border:0;border-radius:12px;background:transparent;';
|
||||
|
||||
frameWrap.appendChild(iframe);
|
||||
panel.appendChild(frameWrap);
|
||||
document.body.appendChild(panel);
|
||||
|
||||
const applyTheme = () => {
|
||||
const frameWrapEl = $('#mega-dashboard-script-frame-wrap');
|
||||
if (!frameWrapEl) return;
|
||||
|
||||
const bodyStyles = window.getComputedStyle(document.body);
|
||||
const borderColor =
|
||||
bodyStyles.getPropertyValue('--border-color') ||
|
||||
bodyStyles.getPropertyValue('--color-border') ||
|
||||
'rgba(24, 34, 47, 0.12)';
|
||||
|
||||
frameWrapEl.style.padding = String(Number(CFG.padding) || 0) + 'px';
|
||||
frameWrapEl.style.background = 'transparent';
|
||||
frameWrapEl.style.border = CFG.showBorder ? '1px solid ' + borderColor.trim() : '0';
|
||||
frameWrapEl.style.boxSizing = 'border-box';
|
||||
};
|
||||
|
||||
const layout = () => {
|
||||
const sidebar = findSidebar();
|
||||
if (!sidebar) return;
|
||||
|
||||
const rect = sidebar.getBoundingClientRect();
|
||||
panel.style.left = rect.right + 'px';
|
||||
panel.style.width = Math.max(window.innerWidth - rect.right, 0) + 'px';
|
||||
panel.style.height = window.innerHeight + 'px';
|
||||
};
|
||||
|
||||
window.__megaDashboardScriptShow = () => {
|
||||
iframe.src = CFG.iframeUrl;
|
||||
layout();
|
||||
applyTheme();
|
||||
panel.style.display = 'block';
|
||||
setLinkActive(true);
|
||||
};
|
||||
|
||||
window.__megaDashboardScriptHide = () => {
|
||||
panel.style.display = 'none';
|
||||
iframe.src = 'about:blank';
|
||||
setLinkActive(false);
|
||||
};
|
||||
|
||||
applyTheme();
|
||||
layout();
|
||||
window.addEventListener('resize', layout, { passive: true });
|
||||
window.addEventListener('resize', applyTheme, { passive: true });
|
||||
new MutationObserver(() => {
|
||||
applyTheme();
|
||||
layout();
|
||||
}).observe(document.body, { attributes: true, childList: true, subtree: true });
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
function ensureSidebarLink() {
|
||||
if ($('#mega-dashboard-script-link')) return;
|
||||
|
||||
const item = document.createElement('li');
|
||||
item.id = 'mega-dashboard-script-link';
|
||||
item.style.listStyle = 'none';
|
||||
|
||||
item.innerHTML = '<a id="mega-dashboard-script-link-anchor" href="javascript:void(0)" style="display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:8px;text-decoration:none;color:inherit;"><span></span><span></span></a>';
|
||||
|
||||
const link = $('#mega-dashboard-script-link-anchor', item);
|
||||
const icon = $('span:first-child', item);
|
||||
const text = $('span:last-child', item);
|
||||
|
||||
if (icon) icon.textContent = CFG.linkIcon || 'M';
|
||||
if (text) text.textContent = CFG.linkText || 'Mega Script';
|
||||
|
||||
let parentList = null;
|
||||
let reference = null;
|
||||
|
||||
if (CFG.positionMode === 'afterLabel' || CFG.positionMode === 'beforeLabel') {
|
||||
const found = findListAndReferenceByLabel(CFG.positionLabel);
|
||||
parentList = found.list;
|
||||
reference = found.ref;
|
||||
}
|
||||
|
||||
if (!parentList) parentList = findAnyList();
|
||||
if (!parentList) return;
|
||||
|
||||
const items = $$(':scope > li', parentList);
|
||||
|
||||
switch (CFG.positionMode) {
|
||||
case 'start':
|
||||
parentList.insertBefore(item, items[0] || null);
|
||||
break;
|
||||
case 'index':
|
||||
parentList.insertBefore(
|
||||
item,
|
||||
items[Math.max(0, Math.min(Number(CFG.positionIndex) || 0, items.length))] || null,
|
||||
);
|
||||
break;
|
||||
case 'afterLabel':
|
||||
if (reference && reference.nextSibling) parentList.insertBefore(item, reference.nextSibling);
|
||||
else parentList.appendChild(item);
|
||||
break;
|
||||
case 'beforeLabel':
|
||||
if (reference) parentList.insertBefore(item, reference);
|
||||
else parentList.insertBefore(item, items[0] || null);
|
||||
break;
|
||||
case 'end':
|
||||
default:
|
||||
parentList.appendChild(item);
|
||||
break;
|
||||
}
|
||||
|
||||
if (link) {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const panel = ensurePanel();
|
||||
if (!panel) return;
|
||||
|
||||
if (panel.style.display === 'block') {
|
||||
window.__megaDashboardScriptHide?.();
|
||||
} else {
|
||||
window.__megaDashboardScriptShow?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function bindSidebarNavigationHide() {
|
||||
if (!CFG.autoHideOnNavigation) return;
|
||||
|
||||
const sidebar = findSidebar();
|
||||
if (!sidebar || sidebar.dataset.megaDashboardScriptHideBound === '1') return;
|
||||
|
||||
sidebar.dataset.megaDashboardScriptHideBound = '1';
|
||||
sidebar.addEventListener(
|
||||
'click',
|
||||
(event) => {
|
||||
const target = event.target;
|
||||
const ownLink = $('#mega-dashboard-script-link');
|
||||
if (ownLink && target instanceof Node && ownLink.contains(target)) return;
|
||||
window.__megaDashboardScriptHide?.();
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
function boot() {
|
||||
ensurePanel();
|
||||
ensureSidebarLink();
|
||||
bindSidebarNavigationHide();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot, { once: true });
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
|
||||
new MutationObserver(() => {
|
||||
ensureSidebarLink();
|
||||
bindSidebarNavigationHide();
|
||||
}).observe(document.body, { childList: true, subtree: true });
|
||||
})();`;
|
||||
}
|
||||
|
||||
export class MegaDashboardScript implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Mega Dashboard Script',
|
||||
name: 'megaDashboardScript',
|
||||
icon: { light: 'file:../../icons/mega.svg', dark: 'file:../../icons/mega.dark.svg' },
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"]}}',
|
||||
description: 'Generate an injectable Mega dashboard script for sidebar buttons and embedded panels',
|
||||
defaults: {
|
||||
name: 'Mega Dashboard Script',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
usableAsTool: true,
|
||||
credentials: [
|
||||
{
|
||||
name: 'megaDashboardScriptApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Generate Config',
|
||||
value: 'generateConfig',
|
||||
description: 'Generate config and setup instructions for a Mega dashboard script',
|
||||
action: 'Generate config and setup instructions for a mega dashboard script',
|
||||
},
|
||||
{
|
||||
name: 'Generate Script',
|
||||
value: 'generateScript',
|
||||
description: 'Generate the injected JavaScript for a Mega dashboard script URL',
|
||||
action: 'Generate the injected script for a mega dashboard script URL',
|
||||
},
|
||||
],
|
||||
default: 'generateConfig',
|
||||
},
|
||||
{
|
||||
displayName: 'Link Text',
|
||||
name: 'linkText',
|
||||
type: 'string',
|
||||
default: 'Meu Aplicativo',
|
||||
required: true,
|
||||
description: 'Visible label used for the sidebar link',
|
||||
},
|
||||
{
|
||||
displayName: 'Link Icon',
|
||||
name: 'linkIcon',
|
||||
type: 'string',
|
||||
default: 'M',
|
||||
description: 'Short icon text displayed beside the sidebar link label',
|
||||
},
|
||||
{
|
||||
displayName: 'Iframe URL',
|
||||
name: 'iframeUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optional iframe URL override. Leave empty to use the credential Default Iframe URL.',
|
||||
},
|
||||
{
|
||||
displayName: 'Position Mode',
|
||||
name: 'positionMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{ name: 'After Label', value: 'afterLabel' },
|
||||
{ name: 'Before Label', value: 'beforeLabel' },
|
||||
{ name: 'End', value: 'end' },
|
||||
{ name: 'Index', value: 'index' },
|
||||
{ name: 'Start', value: 'start' },
|
||||
],
|
||||
default: 'afterLabel',
|
||||
description: 'How the link should be inserted into the Mega sidebar',
|
||||
},
|
||||
{
|
||||
displayName: 'Position Label',
|
||||
name: 'positionLabel',
|
||||
type: 'string',
|
||||
default: 'Conversas',
|
||||
description: 'Reference label used by the beforeLabel and afterLabel modes',
|
||||
displayOptions: {
|
||||
show: {
|
||||
positionMode: ['afterLabel', 'beforeLabel'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Position Index',
|
||||
name: 'positionIndex',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberPrecision: 0,
|
||||
},
|
||||
default: 0,
|
||||
description: 'Index used when the position mode is index',
|
||||
displayOptions: {
|
||||
show: {
|
||||
positionMode: ['index'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Panel Padding',
|
||||
name: 'panelPadding',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Padding applied around the embedded panel in pixels',
|
||||
},
|
||||
{
|
||||
displayName: 'Show Border',
|
||||
name: 'showBorder',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to render a border around the embedded panel',
|
||||
},
|
||||
{
|
||||
displayName: 'Auto Hide On Navigation',
|
||||
name: 'autoHideOnNavigation',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to hide the embedded panel when another sidebar item is clicked',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const credentials = await this.getCredentials('megaDashboardScriptApi');
|
||||
const baseScriptUrl = (credentials.baseScriptUrl as string).trim();
|
||||
const defaultIframeUrl = (credentials.defaultIframeUrl as string).trim();
|
||||
const allowedMegaOrigin = normalizeOrigin(credentials.allowedMegaOrigin as string);
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const operation = this.getNodeParameter('operation', itemIndex) as string;
|
||||
const linkText = (this.getNodeParameter('linkText', itemIndex) as string).trim();
|
||||
const linkIcon = (this.getNodeParameter('linkIcon', itemIndex) as string).trim();
|
||||
const iframeUrl =
|
||||
((this.getNodeParameter('iframeUrl', itemIndex, '') as string) || defaultIframeUrl).trim();
|
||||
const positionMode = this.getNodeParameter('positionMode', itemIndex) as string;
|
||||
const positionLabel = (this.getNodeParameter('positionLabel', itemIndex, '') as string).trim();
|
||||
const positionIndex = this.getNodeParameter('positionIndex', itemIndex, 0) as number;
|
||||
const panelPadding = this.getNodeParameter('panelPadding', itemIndex, 0) as number;
|
||||
const showBorder = this.getNodeParameter('showBorder', itemIndex, true) as boolean;
|
||||
const autoHideOnNavigation = this.getNodeParameter(
|
||||
'autoHideOnNavigation',
|
||||
itemIndex,
|
||||
true,
|
||||
) as boolean;
|
||||
|
||||
const scriptConfig = {
|
||||
allowedMegaOrigin,
|
||||
autoHideOnNavigation,
|
||||
iframeUrl,
|
||||
linkIcon,
|
||||
linkText,
|
||||
padding: panelPadding,
|
||||
positionIndex,
|
||||
positionLabel,
|
||||
positionMode,
|
||||
showBorder,
|
||||
};
|
||||
|
||||
let response: IDataObject;
|
||||
|
||||
if (operation === 'generateScript') {
|
||||
response = {
|
||||
content_type: 'application/javascript; charset=utf-8',
|
||||
javascript: buildDashboardScript(scriptConfig),
|
||||
script_url: baseScriptUrl,
|
||||
iframe_url: iframeUrl,
|
||||
allowed_mega_origin: allowedMegaOrigin,
|
||||
};
|
||||
} else {
|
||||
const inlineLoader = `(() => {
|
||||
const script = document.createElement('script');
|
||||
script.src = ${JSON.stringify(baseScriptUrl)};
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
})();`;
|
||||
|
||||
response = {
|
||||
script_url: baseScriptUrl,
|
||||
iframe_url: iframeUrl,
|
||||
allowed_mega_origin: allowedMegaOrigin,
|
||||
inline_loader_javascript: inlineLoader,
|
||||
script_config: scriptConfig,
|
||||
manual_setup_steps: [
|
||||
'Create a public n8n webhook that returns the generated JavaScript with content-type application/javascript.',
|
||||
'Use the Base Script URL in the Mega dashboard script configuration.',
|
||||
'If Mega expects inline code instead of a URL, use the generated inline loader JavaScript.',
|
||||
'Point the iframe URL to the app or page that should open inside the Mega panel.',
|
||||
'Reload the Mega dashboard and confirm the sidebar link appears in the configured position.',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
returnData.push({ json: response });
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
|
|
@ -38,14 +38,10 @@
|
|||
"strict": true,
|
||||
"credentials": [
|
||||
"dist/credentials/MegaApi.credentials.js",
|
||||
"dist/credentials/MegaDashboardAppApi.credentials.js",
|
||||
"dist/credentials/MegaDashboardScriptApi.credentials.js",
|
||||
"dist/credentials/MegaClientApi.credentials.js",
|
||||
"dist/credentials/MegaPlatformApi.credentials.js"
|
||||
],
|
||||
"nodes": [
|
||||
"dist/nodes/MegaDashboardApp/MegaDashboardApp.node.js",
|
||||
"dist/nodes/MegaDashboardScript/MegaDashboardScript.node.js",
|
||||
"dist/nodes/Mega/Mega.node.js",
|
||||
"dist/nodes/MegaClient/MegaClient.node.js",
|
||||
"dist/nodes/MegaPlatform/MegaPlatform.node.js"
|
||||
|
|
|
|||
Loading…
Reference in a new issue