mirror of
https://github.com/aljazceru/n8n-nodes-signal-cli.git
synced 2025-12-17 06:14:20 +01:00
🤖🌏
This commit is contained in:
10
.clinerules/testing-preferences.md
Normal file
10
.clinerules/testing-preferences.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## Brief overview
|
||||||
|
This set of guidelines outlines the testing preferences for the project, focusing on the avoidance of mocks and specific testing practices.
|
||||||
|
|
||||||
|
## Testing strategies
|
||||||
|
- Never use mocks and jest.mock in tests.
|
||||||
|
- Prefer black-box tests over white-box tests when possible.
|
||||||
|
|
||||||
|
## Testing best practices
|
||||||
|
- Ensure each test is comprehensive and covers all necessary scenarios.
|
||||||
|
- Keep tests concise and focused on specific functionality.
|
||||||
52
.eslintrc.js
Normal file
52
.eslintrc.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
sourceType: 'module',
|
||||||
|
extraFileExtensions: ['.json'],
|
||||||
|
},
|
||||||
|
|
||||||
|
ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'],
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['package.json'],
|
||||||
|
plugins: ['eslint-plugin-n8n-nodes-base'],
|
||||||
|
extends: ['plugin:n8n-nodes-base/community'],
|
||||||
|
rules: {
|
||||||
|
'n8n-nodes-base/community-package-json-name-still-default': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['./credentials/**/*.ts'],
|
||||||
|
plugins: ['eslint-plugin-n8n-nodes-base'],
|
||||||
|
extends: ['plugin:n8n-nodes-base/credentials'],
|
||||||
|
rules: {
|
||||||
|
'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off',
|
||||||
|
'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['./nodes/**/*.ts'],
|
||||||
|
plugins: ['eslint-plugin-n8n-nodes-base'],
|
||||||
|
extends: ['plugin:n8n-nodes-base/nodes'],
|
||||||
|
rules: {
|
||||||
|
'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off',
|
||||||
|
'n8n-nodes-base/node-resource-description-filename-against-convention': 'off',
|
||||||
|
'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
16
.eslintrc.prepublish.js
Normal file
16
.eslintrc.prepublish.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
extends: "./.eslintrc.js",
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['package.json'],
|
||||||
|
plugins: ['eslint-plugin-n8n-nodes-base'],
|
||||||
|
rules: {
|
||||||
|
'n8n-nodes-base/community-package-json-name-still-default': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
node_modules
|
||||||
|
*.tsbuildinfo
|
||||||
|
*.map
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
test
|
||||||
|
tests
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
|
*.tgz
|
||||||
|
.envrc
|
||||||
|
.env
|
||||||
16
.npmignore
Normal file
16
.npmignore
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
node_modules
|
||||||
|
*.ts
|
||||||
|
*.tsbuildinfo
|
||||||
|
*.map
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
test
|
||||||
|
tests
|
||||||
|
*.tgz
|
||||||
|
.envrc
|
||||||
|
.env
|
||||||
102
README.md
Normal file
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# 📦 n8n-nodes-signal-cli
|
||||||
|
|
||||||
|
This repository contains n8n nodes for interacting with Signal CLI. It includes a trigger node for receiving messages and an action node for various Signal operations.
|
||||||
|
|
||||||
|
## 📚 Table of Contents
|
||||||
|
1. [📋 Prerequisites](#-prerequisites)
|
||||||
|
2. [🚀 Usage](#-usage)
|
||||||
|
3. [📥 Installation](#-installation)
|
||||||
|
4. [🤖 Nodes](#-nodes)
|
||||||
|
5. [💻 Development](#-development)
|
||||||
|
6. [🚀 Release](#-release)
|
||||||
|
7. [🤝 Contributing](#-contributing)
|
||||||
|
8. [⚠️ Known Limitations](#-known-limitations)
|
||||||
|
9. [📄 License](#-license)
|
||||||
|
|
||||||
|
## 📋 Prerequisites
|
||||||
|
|
||||||
|
* Node.js (>=18.10) and pnpm (>=9.1)
|
||||||
|
* n8n installed globally using `pnpm install n8n -g`
|
||||||
|
* Signal CLI set up and running in daemon mode with HTTP JSON-RPC endpoint exposed (`--http`)
|
||||||
|
|
||||||
|
|
||||||
|
## 📥 Installation
|
||||||
|
|
||||||
|
1. Clone this repository.
|
||||||
|
2. Run `pnpm install` to install dependencies.
|
||||||
|
3. Run `pnpm build` to build the nodes.
|
||||||
|
4. Copy the `dist` folder and `package.json` to your n8n custom nodes directory (usually `~/.n8n/custom/nodes/n8n-nodes-signal-cli`).
|
||||||
|
|
||||||
|
## 🤖 Nodes
|
||||||
|
|
||||||
|
### 🔔 SignalTrigger
|
||||||
|
|
||||||
|
* Triggers when a new message is received via Signal CLI.
|
||||||
|
* Requires Signal CLI API credential.
|
||||||
|
* Parameters:
|
||||||
|
* `account`: Signal account to listen for incoming messages.
|
||||||
|
|
||||||
|
### 📱 Signal
|
||||||
|
|
||||||
|
* Interact with Signal CLI API for various operations.
|
||||||
|
* Requires Signal CLI API credential.
|
||||||
|
* Supports the following resources and operations:
|
||||||
|
* **Message**:
|
||||||
|
* Send: Send a message to a recipient or group.
|
||||||
|
* Parameters: `account`, `recipient`, `message`
|
||||||
|
* **Group**:
|
||||||
|
* Create: Create a new group.
|
||||||
|
* Parameters: `account`, `name`, `members`
|
||||||
|
* List: List all groups.
|
||||||
|
* Parameters: `account`
|
||||||
|
* **Contact**:
|
||||||
|
* Update: Update a contact's name.
|
||||||
|
* Parameters: `account`, `recipient`, `name`
|
||||||
|
* List: List all contacts.
|
||||||
|
* Parameters: `account`
|
||||||
|
* **Reaction**:
|
||||||
|
* Send: Send a reaction to a message.
|
||||||
|
* Parameters: `account`, `recipient`, `reaction`, `targetAuthor`, `timestamp`
|
||||||
|
* Remove: Remove a reaction from a message.
|
||||||
|
* Parameters: `account`, `recipient`, `reaction`, `targetAuthor`, `timestamp`
|
||||||
|
* **Receipt**:
|
||||||
|
* Send: Send a receipt (read or viewed) for a message.
|
||||||
|
* Parameters: `account`, `recipient`, `receiptType`, `timestamp`
|
||||||
|
|
||||||
|
## 💻 Development
|
||||||
|
|
||||||
|
* Run `pnpm dev` to start the TypeScript compiler in watch mode.
|
||||||
|
* Run `pnpm lint` to check for linting errors.
|
||||||
|
* Run `pnpm test` to run integration tests.
|
||||||
|
|
||||||
|
Before running the tests, set the `ENDPOINT` environment variable to the Signal CLI API URL (e.g., "http://127.0.0.1:8085").
|
||||||
|
|
||||||
|
For example, you can run the following command in your terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ENDPOINT="http://127.0.0.1:8085" # signal-cli endpoint
|
||||||
|
export ACCOUNT_NUMBER="±33620382719" # your phone number in international format
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 🚀 Release
|
||||||
|
|
||||||
|
* Run `pnpm release` to release a new version of the package.
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please follow these steps to contribute:
|
||||||
|
1. Fork this repository.
|
||||||
|
2. Create a new branch for your feature or bug fix.
|
||||||
|
3. Submit a pull request with a clear description of your changes.
|
||||||
|
4. Ensure that your code adheres to the existing coding standards and passes all tests.
|
||||||
|
|
||||||
|
## ⚠️ Known Limitations
|
||||||
|
|
||||||
|
* This implementation relies on the Signal CLI API, which may have its own limitations and constraints.
|
||||||
|
* Ensure that the Signal CLI is properly configured and running before using these nodes.
|
||||||
|
* Some operations may require specific permissions or settings within Signal CLI.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT
|
||||||
26
credentials/SignalCliApi.credentials.ts
Normal file
26
credentials/SignalCliApi.credentials.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class SignalCliApi implements ICredentialType {
|
||||||
|
name = 'signalCliApi';
|
||||||
|
displayName = 'Signal CLI API';
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
default: process.env.ENDPOINT || '',
|
||||||
|
placeholder: 'http://localhost:8085',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
16
gulpfile.js
Normal file
16
gulpfile.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { task, src, dest } = require('gulp');
|
||||||
|
|
||||||
|
task('build:icons', copyIcons);
|
||||||
|
|
||||||
|
function copyIcons() {
|
||||||
|
const nodeSource = path.resolve('nodes', '**', '*.{png,svg}');
|
||||||
|
const nodeDestination = path.resolve('dist', 'nodes');
|
||||||
|
|
||||||
|
src(nodeSource).pipe(dest(nodeDestination));
|
||||||
|
|
||||||
|
const credSource = path.resolve('credentials', '**', '*.{png,svg}');
|
||||||
|
const credDestination = path.resolve('dist', 'credentials');
|
||||||
|
|
||||||
|
return src(credSource).pipe(dest(credDestination));
|
||||||
|
}
|
||||||
9
index.js
Normal file
9
index.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
nodes: {
|
||||||
|
Signal: require('./dist/nodes/Signal/Signal.node').Signal,
|
||||||
|
SignalTrigger: require('./dist/nodes/SignalTrigger/SignalTrigger.node').SignalTrigger,
|
||||||
|
},
|
||||||
|
credentials: {
|
||||||
|
SignalCliApi: require('./dist/credentials/signalCliApi.credentials').SignalCliApi,
|
||||||
|
},
|
||||||
|
};
|
||||||
7
jest.config.js
Normal file
7
jest.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
||||||
|
prettierPath: require.resolve('prettier'),
|
||||||
|
testTimeout: 20000,
|
||||||
|
};
|
||||||
554
nodes/Signal/Signal.node.ts
Normal file
554
nodes/Signal/Signal.node.ts
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
NodeOperationError,
|
||||||
|
NodeConnectionType,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import Debug from 'debug';
|
||||||
|
|
||||||
|
const debug = Debug('n8n:signal');
|
||||||
|
|
||||||
|
export class Signal implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Signal',
|
||||||
|
name: 'signal',
|
||||||
|
group: ['output'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Interact with Signal CLI API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Signal',
|
||||||
|
},
|
||||||
|
inputs: [NodeConnectionType.Main],
|
||||||
|
outputs: [NodeConnectionType.Main],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'signalCliApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Message',
|
||||||
|
value: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Group',
|
||||||
|
value: 'group',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Contact',
|
||||||
|
value: 'contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reaction',
|
||||||
|
value: 'reaction',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Receipt',
|
||||||
|
value: 'receipt',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'message',
|
||||||
|
},
|
||||||
|
// Message properties
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipient',
|
||||||
|
name: 'recipient',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Phone number or group ID of the recipient',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The message to be sent',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Group properties
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'List',
|
||||||
|
value: 'list',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The name of the group',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Members',
|
||||||
|
name: 'members',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Comma-separated list of members to add to the group',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Contact properties
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'List',
|
||||||
|
value: 'list',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'update',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipient',
|
||||||
|
name: 'recipient',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Phone number of the contact',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The name of the contact',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Reaction properties
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['reaction'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove',
|
||||||
|
value: 'remove',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['reaction'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipient',
|
||||||
|
name: 'recipient',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Phone number or group ID of the recipient',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['reaction'],
|
||||||
|
operation: ['send', 'remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reaction',
|
||||||
|
name: 'reaction',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The reaction to be sent',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['reaction'],
|
||||||
|
operation: ['send', 'remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Target Author',
|
||||||
|
name: 'targetAuthor',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The author of the message being reacted to',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['reaction'],
|
||||||
|
operation: ['send', 'remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Timestamp',
|
||||||
|
name: 'timestamp',
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
required: true,
|
||||||
|
description: 'The timestamp of the message being reacted to',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['reaction'],
|
||||||
|
operation: ['send', 'remove'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Receipt properties
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['receipt'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['receipt'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipient',
|
||||||
|
name: 'recipient',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Phone number or group ID of the recipient',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['receipt'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Receipt Type',
|
||||||
|
name: 'receiptType',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Read',
|
||||||
|
value: 'read',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Viewed',
|
||||||
|
value: 'viewed',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'read',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['receipt'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Timestamp',
|
||||||
|
name: 'timestamp',
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
required: true,
|
||||||
|
description: 'The timestamp of the message being receipted',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['receipt'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
const credentials = await this.getCredentials('signalCliApi');
|
||||||
|
|
||||||
|
if (!credentials.url) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Signal CLI API URL is not set in credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${credentials.url}/api/v1/rpc`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
debug('Signal Node: Executing with resource=%s, operation=%s', resource, operation);
|
||||||
|
if (resource === 'message' && operation === 'send') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
const recipient = this.getNodeParameter('recipient', 0) as string;
|
||||||
|
const message = this.getNodeParameter('message', 0) as string;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'send',
|
||||||
|
params: { account, recipient, message },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
debug('Signal Node: Sending message with requestBody=%o', requestBody);
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'group' && operation === 'create') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
const name = this.getNodeParameter('name', 0) as string;
|
||||||
|
const members = (this.getNodeParameter('members', 0) as string).split(',');
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'updateGroup',
|
||||||
|
params: {
|
||||||
|
account,
|
||||||
|
name,
|
||||||
|
members,
|
||||||
|
},
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
debug('Signal Node: Creating group with requestBody=%o', requestBody);
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'group' && operation === 'list') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'listGroups',
|
||||||
|
params: { account },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'contact' && operation === 'update') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
const recipient = this.getNodeParameter('recipient', 0) as string;
|
||||||
|
const name = this.getNodeParameter('name', 0) as string;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'updateContact',
|
||||||
|
params: { account, recipient, name },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'contact' && operation === 'list') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'listContacts',
|
||||||
|
params: { account },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'reaction' && operation === 'send') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
const recipient = this.getNodeParameter('recipient', 0) as string;
|
||||||
|
const reaction = this.getNodeParameter('reaction', 0) as string;
|
||||||
|
const targetAuthor = this.getNodeParameter('targetAuthor', 0) as string;
|
||||||
|
const timestamp = this.getNodeParameter('timestamp', 0) as number;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'sendReaction',
|
||||||
|
params: { account, recipient, reaction, targetAuthor, timestamp },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'reaction' && operation === 'remove') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
const recipient = this.getNodeParameter('recipient', 0) as string;
|
||||||
|
const reaction = this.getNodeParameter('reaction', 0) as string;
|
||||||
|
const targetAuthor = this.getNodeParameter('targetAuthor', 0) as string;
|
||||||
|
const timestamp = this.getNodeParameter('timestamp', 0) as number;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'sendReaction',
|
||||||
|
params: { account, recipient, reaction, targetAuthor, timestamp, remove: true },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
} else if (resource === 'receipt' && operation === 'send') {
|
||||||
|
const account = this.getNodeParameter('account', 0) as string;
|
||||||
|
const recipient = this.getNodeParameter('recipient', 0) as string;
|
||||||
|
const receiptType = this.getNodeParameter('receiptType', 0) as string;
|
||||||
|
const timestamp = this.getNodeParameter('timestamp', 0) as number;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'sendReceipt',
|
||||||
|
params: { account, recipient, receiptType, timestamp },
|
||||||
|
id: uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await axios.post(`${url}`, requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Signal Node: Response', response?.data);
|
||||||
|
const item: INodeExecutionData = {
|
||||||
|
json: response?.data,
|
||||||
|
};
|
||||||
|
return [[item]];
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Error interacting with Signal API', {
|
||||||
|
itemIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
202
nodes/Signal/Signal.test.ts
Normal file
202
nodes/Signal/Signal.test.ts
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import { Signal } from "./Signal.node";
|
||||||
|
import type { IExecuteFunctions } from "n8n-workflow";
|
||||||
|
import omit from "omit-deep";
|
||||||
|
|
||||||
|
jest.mock("uuid", () => ({ v4: () => "n8n" }));
|
||||||
|
|
||||||
|
describe("Signal Node", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send a message", async () => {
|
||||||
|
const signal = new Signal();
|
||||||
|
const executeFunctions = {
|
||||||
|
getCredentials: async () => ({
|
||||||
|
url: process.env.ENDPOINT,
|
||||||
|
account: process.env.ACCOUNT_NUMBER,
|
||||||
|
}),
|
||||||
|
getNodeParameter: (paramName: string): string => {
|
||||||
|
if (paramName === "account")
|
||||||
|
return process.env.ACCOUNT_NUMBER as string;
|
||||||
|
if (paramName === "recipient")
|
||||||
|
return process.env.ACCOUNT_NUMBER as string;
|
||||||
|
if (paramName === "message") return "Hello, world!";
|
||||||
|
if (paramName === "resource") return "message";
|
||||||
|
if (paramName === "operation") return "send";
|
||||||
|
throw new Error(`Unexpected parameter name: ${paramName}`);
|
||||||
|
},
|
||||||
|
helpers: {},
|
||||||
|
logger: {
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
} as any,
|
||||||
|
getExecutionId: jest.fn(),
|
||||||
|
getNode: jest.fn(),
|
||||||
|
continueOnFail: jest.fn(),
|
||||||
|
getInputData: jest.fn(),
|
||||||
|
getWorkflowStaticData: jest.fn(),
|
||||||
|
getRestApiUrl: jest.fn(),
|
||||||
|
getTimezone: jest.fn(),
|
||||||
|
getWorkflow: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions;
|
||||||
|
|
||||||
|
const result = await signal.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
omit(result[0][0].json, [
|
||||||
|
"timestamp",
|
||||||
|
"result.results.0.recipientAddress.uuid",
|
||||||
|
"result.results.0.recipientAddress.number",
|
||||||
|
])
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"id": "n8n",
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"recipientAddress": {},
|
||||||
|
"type": "SUCCESS",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a group", async () => {
|
||||||
|
const signal = new Signal();
|
||||||
|
const executeFunctions = {
|
||||||
|
getCredentials: async () => ({
|
||||||
|
url: process.env.ENDPOINT,
|
||||||
|
account: process.env.ACCOUNT_NUMBER,
|
||||||
|
}),
|
||||||
|
getNodeParameter: (paramName: string): string => {
|
||||||
|
if (paramName === "account")
|
||||||
|
return process.env.ACCOUNT_NUMBER as string;
|
||||||
|
if (paramName === "name") return "Test Group";
|
||||||
|
if (paramName === "members") return `${process.env.ACCOUNT_NUMBER}`;
|
||||||
|
if (paramName === "resource") return "group";
|
||||||
|
if (paramName === "operation") return "create";
|
||||||
|
throw new Error(`Unexpected parameter name: ${paramName}`);
|
||||||
|
},
|
||||||
|
helpers: {},
|
||||||
|
logger: {
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
} as any,
|
||||||
|
getExecutionId: jest.fn(),
|
||||||
|
getNode: jest.fn(),
|
||||||
|
continueOnFail: jest.fn(),
|
||||||
|
getInputData: jest.fn(),
|
||||||
|
getWorkflowStaticData: jest.fn(),
|
||||||
|
getRestApiUrl: jest.fn(),
|
||||||
|
getTimezone: jest.fn(),
|
||||||
|
getWorkflow: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions;
|
||||||
|
|
||||||
|
const result = await signal.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(omit(result[0][0].json, ["timestamp", "result.groupId"]))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"id": "n8n",
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"results": [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should list groups", async () => {
|
||||||
|
const signal = new Signal();
|
||||||
|
const executeFunctions = {
|
||||||
|
getCredentials: async () => ({
|
||||||
|
url: process.env.ENDPOINT,
|
||||||
|
account: process.env.ACCOUNT_NUMBER,
|
||||||
|
}),
|
||||||
|
getNodeParameter: (paramName: string): string => {
|
||||||
|
if (paramName === "account")
|
||||||
|
return process.env.ACCOUNT_NUMBER as string;
|
||||||
|
if (paramName === "resource") return "group";
|
||||||
|
if (paramName === "operation") return "list";
|
||||||
|
throw new Error(`Unexpected parameter name: ${paramName}`);
|
||||||
|
},
|
||||||
|
helpers: {},
|
||||||
|
logger: {
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
} as any,
|
||||||
|
getExecutionId: jest.fn(),
|
||||||
|
getNode: jest.fn(),
|
||||||
|
continueOnFail: jest.fn(),
|
||||||
|
getInputData: jest.fn(),
|
||||||
|
getWorkflowStaticData: jest.fn(),
|
||||||
|
getRestApiUrl: jest.fn(),
|
||||||
|
getTimezone: jest.fn(),
|
||||||
|
getWorkflow: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions;
|
||||||
|
|
||||||
|
const result = await signal.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect((result?.[0]?.[0]?.json?.result as any[]).length).toBeGreaterThan(0);
|
||||||
|
expect(omit(result[0][0].json, ["timestamp", "result"]))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"id": "n8n",
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should list contacts", async () => {
|
||||||
|
const signal = new Signal();
|
||||||
|
const executeFunctions = {
|
||||||
|
getCredentials: async () => ({
|
||||||
|
url: process.env.ENDPOINT,
|
||||||
|
account: process.env.ACCOUNT_NUMBER,
|
||||||
|
}),
|
||||||
|
getNodeParameter: (paramName: string): string => {
|
||||||
|
if (paramName === "account")
|
||||||
|
return process.env.ACCOUNT_NUMBER as string;
|
||||||
|
if (paramName === "resource") return "contact";
|
||||||
|
if (paramName === "operation") return "list";
|
||||||
|
throw new Error(`Unexpected parameter name: ${paramName}`);
|
||||||
|
},
|
||||||
|
helpers: {},
|
||||||
|
logger: {
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
} as any,
|
||||||
|
getExecutionId: jest.fn(),
|
||||||
|
getNode: jest.fn(),
|
||||||
|
continueOnFail: jest.fn(),
|
||||||
|
getInputData: jest.fn(),
|
||||||
|
getWorkflowStaticData: jest.fn(),
|
||||||
|
getRestApiUrl: jest.fn(),
|
||||||
|
getTimezone: jest.fn(),
|
||||||
|
getWorkflow: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions;
|
||||||
|
|
||||||
|
const result = await signal.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect((result?.[0]?.[0]?.json?.result as any[]).length).toBeGreaterThan(0);
|
||||||
|
expect(omit(result?.[0]?.[0]?.json, ["timestamp", "result"]))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"id": "n8n",
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
96
nodes/SignalTrigger/SignalTrigger.node.ts
Normal file
96
nodes/SignalTrigger/SignalTrigger.node.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
ITriggerFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
NodeApiError,
|
||||||
|
ITriggerResponse,
|
||||||
|
NodeConnectionType,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { EventSource } from 'eventsource';
|
||||||
|
import debug from 'debug';
|
||||||
|
|
||||||
|
const signalTriggerDebug = debug('n8n:nodes:signal-trigger');
|
||||||
|
|
||||||
|
export class SignalTrigger implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Signal Trigger',
|
||||||
|
name: 'signalTrigger',
|
||||||
|
group: ['trigger'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Triggers when a new message is received',
|
||||||
|
defaults: {
|
||||||
|
name: 'Signal Trigger',
|
||||||
|
},
|
||||||
|
inputs: [],
|
||||||
|
outputs: [NodeConnectionType.Main],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'signalCliApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||||
|
const credentials = await this.getCredentials('signalCliApi');
|
||||||
|
if (!credentials.url) {
|
||||||
|
throw new NodeApiError(this.getNode(), { message: 'Signal CLI API URL is not set in credentials' });
|
||||||
|
}
|
||||||
|
const url = `${credentials.url}/api/v1/events`;
|
||||||
|
|
||||||
|
const eventSource = new EventSource(url);
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
signalTriggerDebug('Received event: %o', event);
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
const message = data.dataMessage?.message;
|
||||||
|
if (message) {
|
||||||
|
const item: INodeExecutionData = {
|
||||||
|
json: { message },
|
||||||
|
};
|
||||||
|
this.emit([this.helpers.returnJsonArray([item])]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error parsing message from Signal API', { error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
eventSource.onerror = (err) => {
|
||||||
|
this.logger.error('EventSource error');
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
signalTriggerDebug('Connected to %s', url);
|
||||||
|
|
||||||
|
eventSource.onerror = (err) => {
|
||||||
|
this.logger.error('EventSource error', {error: err });
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
closeFunction: async () => {
|
||||||
|
eventSource.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
71
package.json
Normal file
71
package.json
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "n8n-nodes-signal-cli",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "n8n nodes for Signal CLI",
|
||||||
|
"keywords": [
|
||||||
|
"n8n-community-node-package"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "",
|
||||||
|
"author": "Francois-Guillaume Ribreau <npm@fgribreau.com> (https://fgribreau.com)",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/fgribreau/n8n-nodes-signal-cli"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.10",
|
||||||
|
"pnpm": ">=9.1"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@9.1.4",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && gulp build:icons",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"format": "prettier nodes credentials --write",
|
||||||
|
"lint": "eslint nodes/**/*.ts credentials/**/*.ts package.json",
|
||||||
|
"lintfix": "eslint nodes/**/*.ts credentials/**/*.ts package.json --fix",
|
||||||
|
"prepublishOnly": "pnpm build && pnpm lint -c .eslintrc.prepublish.js nodes/**/*.ts credentials/**/*.ts package.json",
|
||||||
|
"release": "pnpm test && pnpm build && np --no-yarn --any-branch",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"n8n": {
|
||||||
|
"n8nNodesApiVersion": 1,
|
||||||
|
"credentials": [
|
||||||
|
"dist/credentials/signalCliApi.credentials.js"
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
"dist/nodes/Signal/Signal.node.js",
|
||||||
|
"dist/nodes/SignalTrigger/SignalTrigger.node.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/axios": "^0.14.4",
|
||||||
|
"@types/debug": "^4.1.12",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/node": "^22.15.3",
|
||||||
|
"@types/omit-deep": "^0.3.2",
|
||||||
|
"@typescript-eslint/parser": "^7.15.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
||||||
|
"gulp": "^5.0.0",
|
||||||
|
"gulp-typescript": "^6.0.0-alpha.1",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"np": "^10.2.0",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
|
"ts-jest": "^29.3.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"n8n-workflow": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"eventsource": "^3.0.6",
|
||||||
|
"omit-deep": "^0.3.0",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
tsconfig.json
Normal file
32
tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "es2019",
|
||||||
|
"lib": ["es2019", "es2020", "es2022.error"],
|
||||||
|
"removeComments": true,
|
||||||
|
"useUnknownInCatchVariables": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"incremental": true,
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"types": ["node", "jest"]
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules/axios"],
|
||||||
|
"include": [
|
||||||
|
"credentials/**/*",
|
||||||
|
"nodes/**/*",
|
||||||
|
"nodes/**/*.json",
|
||||||
|
"package.json",
|
||||||
|
],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user