@limetech/n8n-nodes-lime 3.10.3 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/credentials/LimeCrmApi.credentials.ts +1 -0
- package/credentials/LimeFormsApi.credentials.ts +1 -0
- package/credentials/LimeMarketingApi.credentials.ts +92 -0
- package/credentials/assets/lime-crm.svg +1 -0
- package/credentials/index.ts +1 -0
- package/dist/credentials/LimeCrmApi.credentials.d.ts +1 -0
- package/dist/credentials/LimeCrmApi.credentials.js +1 -0
- package/dist/credentials/LimeCrmApi.credentials.js.map +1 -1
- package/dist/credentials/LimeFormsApi.credentials.d.ts +1 -0
- package/dist/credentials/LimeFormsApi.credentials.js +1 -0
- package/dist/credentials/LimeFormsApi.credentials.js.map +1 -1
- package/dist/credentials/LimeMarketingApi.credentials.d.ts +9 -0
- package/dist/credentials/LimeMarketingApi.credentials.js +64 -0
- package/dist/credentials/LimeMarketingApi.credentials.js.map +1 -0
- package/dist/credentials/assets/lime-crm.svg +1 -0
- package/dist/credentials/index.d.ts +1 -0
- package/dist/credentials/index.js +1 -0
- package/dist/credentials/index.js.map +1 -1
- package/dist/nodes/index.d.ts +1 -0
- package/dist/nodes/index.js +1 -0
- package/dist/nodes/index.js.map +1 -1
- package/dist/nodes/lime-crm/assets/lime-crm.svg +0 -0
- package/dist/nodes/lime-forms/assets/lime-crm.svg +0 -0
- package/dist/nodes/lime-marketing/LimeMarketing.node.d.ts +16 -0
- package/dist/nodes/lime-marketing/LimeMarketing.node.js +121 -0
- package/dist/nodes/lime-marketing/LimeMarketing.node.js.map +1 -0
- package/dist/nodes/lime-marketing/assets/lime-crm.svg +1 -0
- package/dist/nodes/lime-marketing/index.d.ts +1 -0
- package/dist/nodes/lime-marketing/index.js +18 -0
- package/dist/nodes/lime-marketing/index.js.map +1 -0
- package/dist/nodes/lime-marketing/methods/getTemplateMergeCodeMappingColumns.d.ts +3 -0
- package/dist/nodes/lime-marketing/methods/getTemplateMergeCodeMappingColumns.js +52 -0
- package/dist/nodes/lime-marketing/methods/getTemplateMergeCodeMappingColumns.js.map +1 -0
- package/dist/nodes/lime-marketing/methods/getTemplates.d.ts +2 -0
- package/dist/nodes/lime-marketing/methods/getTemplates.js +24 -0
- package/dist/nodes/lime-marketing/methods/getTemplates.js.map +1 -0
- package/dist/nodes/lime-marketing/methods/index.d.ts +2 -0
- package/dist/nodes/lime-marketing/methods/index.js +8 -0
- package/dist/nodes/lime-marketing/methods/index.js.map +1 -0
- package/dist/nodes/lime-marketing/models/constants.d.ts +9 -0
- package/dist/nodes/lime-marketing/models/constants.js +13 -0
- package/dist/nodes/lime-marketing/models/constants.js.map +1 -0
- package/dist/nodes/lime-marketing/models/index.d.ts +6 -0
- package/dist/nodes/lime-marketing/models/index.js +14 -0
- package/dist/nodes/lime-marketing/models/index.js.map +1 -0
- package/dist/nodes/lime-marketing/models/operationInputs.d.ts +36 -0
- package/dist/nodes/lime-marketing/models/operationInputs.js +3 -0
- package/dist/nodes/lime-marketing/models/operationInputs.js.map +1 -0
- package/dist/nodes/lime-marketing/models/template.d.ts +10 -0
- package/dist/nodes/lime-marketing/models/template.js +3 -0
- package/dist/nodes/lime-marketing/models/template.js.map +1 -0
- package/dist/nodes/lime-marketing/models/transactionMail.d.ts +60 -0
- package/dist/nodes/lime-marketing/models/transactionMail.js +3 -0
- package/dist/nodes/lime-marketing/models/transactionMail.js.map +1 -0
- package/dist/nodes/lime-marketing/models/transactionSms.d.ts +20 -0
- package/dist/nodes/lime-marketing/models/transactionSms.js +3 -0
- package/dist/nodes/lime-marketing/models/transactionSms.js.map +1 -0
- package/dist/nodes/lime-marketing/models/transportError.d.ts +7 -0
- package/dist/nodes/lime-marketing/models/transportError.js +3 -0
- package/dist/nodes/lime-marketing/models/transportError.js.map +1 -0
- package/dist/nodes/lime-marketing/resources/transactionmail/index.d.ts +6 -0
- package/dist/nodes/lime-marketing/resources/transactionmail/index.js +56 -0
- package/dist/nodes/lime-marketing/resources/transactionmail/index.js.map +1 -0
- package/dist/nodes/lime-marketing/resources/transactionmail/operations/send.operation.d.ts +10 -0
- package/dist/nodes/lime-marketing/resources/transactionmail/operations/send.operation.js +543 -0
- package/dist/nodes/lime-marketing/resources/transactionmail/operations/send.operation.js.map +1 -0
- package/dist/nodes/lime-marketing/resources/transactionsms/index.d.ts +6 -0
- package/dist/nodes/lime-marketing/resources/transactionsms/index.js +56 -0
- package/dist/nodes/lime-marketing/resources/transactionsms/index.js.map +1 -0
- package/dist/nodes/lime-marketing/resources/transactionsms/operations/send.operation.d.ts +10 -0
- package/dist/nodes/lime-marketing/resources/transactionsms/operations/send.operation.js +171 -0
- package/dist/nodes/lime-marketing/resources/transactionsms/operations/send.operation.js.map +1 -0
- package/dist/nodes/lime-marketing/transport/index.d.ts +7 -0
- package/dist/nodes/lime-marketing/transport/index.js +34 -0
- package/dist/nodes/lime-marketing/transport/index.js.map +1 -0
- package/dist/nodes/lime-marketing/utils/index.d.ts +8 -0
- package/dist/nodes/lime-marketing/utils/index.js +62 -0
- package/dist/nodes/lime-marketing/utils/index.js.map +1 -0
- package/dist/nodes/lime-marketing/utils/mergeCodes.d.ts +26 -0
- package/dist/nodes/lime-marketing/utils/mergeCodes.js +85 -0
- package/dist/nodes/lime-marketing/utils/mergeCodes.js.map +1 -0
- package/dist/package.json +5 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/nodes/index.ts +1 -0
- package/nodes/lime-crm/assets/lime-crm.svg +0 -0
- package/nodes/lime-forms/assets/lime-crm.svg +0 -0
- package/nodes/lime-marketing/LimeMarketing.node.ts +180 -0
- package/nodes/lime-marketing/assets/lime-crm.svg +1 -0
- package/nodes/lime-marketing/index.ts +1 -0
- package/nodes/lime-marketing/methods/getTemplateMergeCodeMappingColumns.ts +70 -0
- package/nodes/lime-marketing/methods/getTemplates.ts +33 -0
- package/nodes/lime-marketing/methods/index.ts +2 -0
- package/nodes/lime-marketing/models/constants.ts +12 -0
- package/nodes/lime-marketing/models/index.ts +32 -0
- package/nodes/lime-marketing/models/operationInputs.ts +40 -0
- package/nodes/lime-marketing/models/template.ts +10 -0
- package/nodes/lime-marketing/models/transactionMail.ts +65 -0
- package/nodes/lime-marketing/models/transactionSms.ts +22 -0
- package/nodes/lime-marketing/models/transportError.ts +6 -0
- package/nodes/lime-marketing/resources/transactionmail/index.ts +22 -0
- package/nodes/lime-marketing/resources/transactionmail/operations/send.operation.ts +664 -0
- package/nodes/lime-marketing/resources/transactionsms/index.ts +22 -0
- package/nodes/lime-marketing/resources/transactionsms/operations/send.operation.ts +228 -0
- package/nodes/lime-marketing/transport/index.ts +68 -0
- package/nodes/lime-marketing/utils/index.ts +139 -0
- package/nodes/lime-marketing/utils/mergeCodes.ts +121 -0
- package/package.json +6 -4
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
import {
|
|
3
|
+
TRANSACTIONAL_SMS_RESOURCE,
|
|
4
|
+
SendTransactionSmsArgs,
|
|
5
|
+
TagModel,
|
|
6
|
+
TransactionSmsModel,
|
|
7
|
+
MERGE_CODES_INPUT_FIELDS,
|
|
8
|
+
MERGE_CODES_INPUT_JSON,
|
|
9
|
+
SEND_OPERATION,
|
|
10
|
+
TagListInput,
|
|
11
|
+
AdditionalSmsOptions,
|
|
12
|
+
} from '../../../models';
|
|
13
|
+
import {
|
|
14
|
+
assertValidSmsDestinationNumber,
|
|
15
|
+
assertValidSmsFromNumber,
|
|
16
|
+
omitEmpty,
|
|
17
|
+
} from '../../../utils';
|
|
18
|
+
import {
|
|
19
|
+
mergeCodesInputMethodProperty,
|
|
20
|
+
mergeCodesTagFieldOptions,
|
|
21
|
+
parseMergeCodesJson,
|
|
22
|
+
readFreeFormTagModels,
|
|
23
|
+
} from '../../../utils/mergeCodes';
|
|
24
|
+
import { limeMarketingRequest } from '../../../transport';
|
|
25
|
+
|
|
26
|
+
/** @public */
|
|
27
|
+
export const description = {
|
|
28
|
+
name: 'Send',
|
|
29
|
+
value: SEND_OPERATION,
|
|
30
|
+
description: 'Send an SMS',
|
|
31
|
+
action: 'Send an SMS',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** @public */
|
|
35
|
+
export const properties: INodeProperties[] = [
|
|
36
|
+
{
|
|
37
|
+
displayName: 'From Number',
|
|
38
|
+
name: 'fromNumber',
|
|
39
|
+
type: 'string',
|
|
40
|
+
required: true,
|
|
41
|
+
default: '',
|
|
42
|
+
placeholder: 'e.g. MyCompany',
|
|
43
|
+
description:
|
|
44
|
+
'The alphanumeric sender ID shown to the recipient (2–11 characters).',
|
|
45
|
+
displayOptions: {
|
|
46
|
+
show: {
|
|
47
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
48
|
+
operation: [SEND_OPERATION],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
displayName: 'Destination Number',
|
|
54
|
+
name: 'destinationNumber',
|
|
55
|
+
type: 'string',
|
|
56
|
+
required: true,
|
|
57
|
+
default: '',
|
|
58
|
+
placeholder: 'e.g. +46701234567',
|
|
59
|
+
description:
|
|
60
|
+
'The recipient phone number in international format, starting with "+".',
|
|
61
|
+
displayOptions: {
|
|
62
|
+
show: {
|
|
63
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
64
|
+
operation: [SEND_OPERATION],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
displayName: 'Text',
|
|
70
|
+
name: 'text',
|
|
71
|
+
type: 'string',
|
|
72
|
+
typeOptions: { rows: 4 },
|
|
73
|
+
required: true,
|
|
74
|
+
default: '',
|
|
75
|
+
description:
|
|
76
|
+
'The SMS body. Use merge codes like {{firstname}} and provide values in Merge Code Mapping below.',
|
|
77
|
+
displayOptions: {
|
|
78
|
+
show: {
|
|
79
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
80
|
+
operation: [SEND_OPERATION],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
mergeCodesInputMethodProperty({
|
|
85
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
86
|
+
operation: [SEND_OPERATION],
|
|
87
|
+
}),
|
|
88
|
+
{
|
|
89
|
+
displayName: 'Merge Code Mapping',
|
|
90
|
+
name: 'textContentTagModels',
|
|
91
|
+
type: 'fixedCollection',
|
|
92
|
+
typeOptions: { multipleValues: true },
|
|
93
|
+
placeholder: 'Add Merge Code',
|
|
94
|
+
default: {},
|
|
95
|
+
description: 'Merge codes and values to substitute into the SMS text.',
|
|
96
|
+
displayOptions: {
|
|
97
|
+
show: {
|
|
98
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
99
|
+
operation: [SEND_OPERATION],
|
|
100
|
+
mergeCodesInputMethod: [MERGE_CODES_INPUT_FIELDS],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
options: mergeCodesTagFieldOptions,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
displayName: 'Merge Codes (JSON)',
|
|
107
|
+
name: 'mergeCodesJson',
|
|
108
|
+
type: 'json',
|
|
109
|
+
required: true,
|
|
110
|
+
default: '{\n "{{firstname}}": "John"\n}',
|
|
111
|
+
description:
|
|
112
|
+
'Object mapping each merge code (with braces) to its replacement value.',
|
|
113
|
+
typeOptions: {
|
|
114
|
+
alwaysOpenEditWindow: true,
|
|
115
|
+
},
|
|
116
|
+
displayOptions: {
|
|
117
|
+
show: {
|
|
118
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
119
|
+
operation: [SEND_OPERATION],
|
|
120
|
+
mergeCodesInputMethod: [MERGE_CODES_INPUT_JSON],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
displayName: 'Additional Options',
|
|
126
|
+
name: 'additionalOptions',
|
|
127
|
+
type: 'collection',
|
|
128
|
+
placeholder: 'Add Option',
|
|
129
|
+
default: {},
|
|
130
|
+
displayOptions: {
|
|
131
|
+
show: {
|
|
132
|
+
resource: [TRANSACTIONAL_SMS_RESOURCE],
|
|
133
|
+
operation: [SEND_OPERATION],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
options: [
|
|
137
|
+
{
|
|
138
|
+
displayName: 'Delivery Timeout Hours',
|
|
139
|
+
name: 'deliveryTimeoutHours',
|
|
140
|
+
type: 'number',
|
|
141
|
+
typeOptions: { minValue: 0, maxValue: 168 },
|
|
142
|
+
default: null,
|
|
143
|
+
description:
|
|
144
|
+
'How long (in hours, 0–168) the provider should keep retrying delivery before giving up.',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
displayName: 'Max Parts',
|
|
148
|
+
name: 'maxParts',
|
|
149
|
+
type: 'number',
|
|
150
|
+
typeOptions: { minValue: 1 },
|
|
151
|
+
default: null,
|
|
152
|
+
description:
|
|
153
|
+
'Maximum number of SMS segments the message is allowed to split into.',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
displayName: 'Scheduled Send Date',
|
|
157
|
+
name: 'scheduledSendDate',
|
|
158
|
+
type: 'dateTime',
|
|
159
|
+
default: '',
|
|
160
|
+
description:
|
|
161
|
+
'When to send the SMS. Leave empty to send immediately.',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
// Read merge codes from whichever UI surface is active (JSON or free-form
|
|
168
|
+
// fixedCollection) and return them as TagModels.
|
|
169
|
+
function readMergeCodes(ctx: IExecuteFunctions, i: number): TagModel[] {
|
|
170
|
+
const method = ctx.getNodeParameter(
|
|
171
|
+
'mergeCodesInputMethod',
|
|
172
|
+
i,
|
|
173
|
+
MERGE_CODES_INPUT_FIELDS
|
|
174
|
+
) as string;
|
|
175
|
+
if (method === MERGE_CODES_INPUT_JSON) return parseMergeCodesJson(ctx, i);
|
|
176
|
+
return readFreeFormTagModels(
|
|
177
|
+
ctx.getNodeParameter('textContentTagModels', i, {}) as TagListInput
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function execute(
|
|
182
|
+
this: IExecuteFunctions,
|
|
183
|
+
i: number,
|
|
184
|
+
baseURL?: string
|
|
185
|
+
) {
|
|
186
|
+
const fromNumber = this.getNodeParameter('fromNumber', i) as string;
|
|
187
|
+
const destinationNumber = this.getNodeParameter(
|
|
188
|
+
'destinationNumber',
|
|
189
|
+
i
|
|
190
|
+
) as string;
|
|
191
|
+
const text = this.getNodeParameter('text', i) as string;
|
|
192
|
+
|
|
193
|
+
assertValidSmsFromNumber(this, fromNumber, 'From Number', i);
|
|
194
|
+
assertValidSmsDestinationNumber(
|
|
195
|
+
this,
|
|
196
|
+
destinationNumber,
|
|
197
|
+
'Destination Number',
|
|
198
|
+
i
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const additional = this.getNodeParameter(
|
|
202
|
+
'additionalOptions',
|
|
203
|
+
i,
|
|
204
|
+
{}
|
|
205
|
+
) as AdditionalSmsOptions;
|
|
206
|
+
|
|
207
|
+
const textContentTagModels = readMergeCodes(this, i);
|
|
208
|
+
|
|
209
|
+
const body: SendTransactionSmsArgs = {
|
|
210
|
+
Text: text,
|
|
211
|
+
FromNumber: fromNumber,
|
|
212
|
+
DestinationNumber: destinationNumber,
|
|
213
|
+
MaxParts: omitEmpty(additional.maxParts),
|
|
214
|
+
DeliveryTimeoutHours: omitEmpty(additional.deliveryTimeoutHours),
|
|
215
|
+
ScheduledSendDate: omitEmpty(additional.scheduledSendDate),
|
|
216
|
+
TextContentTagModels:
|
|
217
|
+
textContentTagModels.length > 0 ? textContentTagModels : undefined,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return limeMarketingRequest<TransactionSmsModel>(this, {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
url: 'transactionsms/send',
|
|
223
|
+
body,
|
|
224
|
+
json: true,
|
|
225
|
+
errorContext: 'send SMS',
|
|
226
|
+
baseURL,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IAllExecuteFunctions,
|
|
3
|
+
IHttpRequestOptions,
|
|
4
|
+
NodeApiError,
|
|
5
|
+
} from 'n8n-workflow';
|
|
6
|
+
import { LIME_MARKETING_API_CREDENTIAL_KEY } from '../models';
|
|
7
|
+
import { getBaseUrl } from '../utils';
|
|
8
|
+
import { handleWorkflowError } from '../../errorHandling';
|
|
9
|
+
import { APIResponse } from '../../response';
|
|
10
|
+
import { LimeMarketingErrorBody } from '../models';
|
|
11
|
+
|
|
12
|
+
const formatErrorDetails = (
|
|
13
|
+
details: LimeMarketingErrorBody['ErrorDetails']
|
|
14
|
+
): string =>
|
|
15
|
+
(details ?? [])
|
|
16
|
+
.map((d) => [d?.Message, d?.Context].filter(Boolean).join(': '))
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join('; ');
|
|
19
|
+
|
|
20
|
+
const createErrorMessage = (error: NodeApiError): string => {
|
|
21
|
+
const data = error.context.data as LimeMarketingErrorBody;
|
|
22
|
+
const message = data.Message ?? '';
|
|
23
|
+
const details = formatErrorDetails(data.ErrorDetails);
|
|
24
|
+
return details ? `${message}: ${details}` : message;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type LimeMarketingRequestOptions = Omit<
|
|
28
|
+
IHttpRequestOptions,
|
|
29
|
+
'baseURL'
|
|
30
|
+
> & {
|
|
31
|
+
/**
|
|
32
|
+
* Action being performed, used to prefix error messages.
|
|
33
|
+
* Example: 'send email' → "Failed to send email: <api message>".
|
|
34
|
+
*/
|
|
35
|
+
errorContext?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Pre-resolved base URL. Pass it once at the top of execute() to avoid
|
|
38
|
+
* re-fetching credentials on every per-item HTTP call.
|
|
39
|
+
*/
|
|
40
|
+
baseURL?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export async function limeMarketingRequest<T>(
|
|
44
|
+
context: IAllExecuteFunctions,
|
|
45
|
+
options: LimeMarketingRequestOptions
|
|
46
|
+
): Promise<APIResponse<T>> {
|
|
47
|
+
const { baseURL: providedBaseURL, ...httpOptions } = options;
|
|
48
|
+
const baseURL = providedBaseURL ?? (await getBaseUrl(context));
|
|
49
|
+
try {
|
|
50
|
+
const data = await context.helpers.httpRequestWithAuthentication.call(
|
|
51
|
+
context,
|
|
52
|
+
LIME_MARKETING_API_CREDENTIAL_KEY,
|
|
53
|
+
{ ...httpOptions, baseURL }
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
data: data,
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return handleWorkflowError(
|
|
61
|
+
context.getNode(),
|
|
62
|
+
{
|
|
63
|
+
message: createErrorMessage(error),
|
|
64
|
+
},
|
|
65
|
+
true
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IAllExecuteFunctions,
|
|
3
|
+
IExecuteFunctions,
|
|
4
|
+
NodeOperationError,
|
|
5
|
+
} from 'n8n-workflow';
|
|
6
|
+
import { LIME_MARKETING_API_CREDENTIAL_KEY, AttachmentModel } from '../models';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Matches the server-side Lime Marketing email regex so we fail fast in the
|
|
10
|
+
* node rather than round-tripping an obviously bad address to the API.
|
|
11
|
+
*
|
|
12
|
+
* Source:
|
|
13
|
+
* EmailUsernamePartRegex = @"([a-zA-Z0-9_\-\.'\+\!\#\$\%\&\?\=\^\{\}\|\~\*]+)"
|
|
14
|
+
* EmailRegexPattern = "^" + username + "@((\[ipv4\])|(([a-zA-Z0-9-_]+\.)*([a-zA-Z0-9-]+)\.[a-zA-Z0-9]{2,15}))$"
|
|
15
|
+
*/
|
|
16
|
+
const LIME_MARKETING_EMAIL_REGEX =
|
|
17
|
+
// eslint-disable-next-line sonarjs/regex-complexity, sonarjs/concise-regex
|
|
18
|
+
/^([a-zA-Z0-9_\-.'+!#$%&?=^{}|~*]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z0-9\-_]+\.)*([a-zA-Z0-9-]+)\.[a-zA-Z0-9]{2,15}))$/;
|
|
19
|
+
|
|
20
|
+
export function assertValidEmail(
|
|
21
|
+
context: IExecuteFunctions,
|
|
22
|
+
email: string,
|
|
23
|
+
fieldLabel: string,
|
|
24
|
+
itemIndex: number
|
|
25
|
+
): void {
|
|
26
|
+
if (LIME_MARKETING_EMAIL_REGEX.test(email)) return;
|
|
27
|
+
throw new NodeOperationError(
|
|
28
|
+
context.getNode(),
|
|
29
|
+
`${fieldLabel} is not a valid email address: ${email}`,
|
|
30
|
+
{
|
|
31
|
+
itemIndex,
|
|
32
|
+
description:
|
|
33
|
+
'Expected a format like name@example.com. Lime Marketing rejects addresses that do not match its email pattern.',
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Destination phone number regex from the /transactionsms/send schema:
|
|
40
|
+
* ^([+]+[1-9]{2})+\d{5,20}$
|
|
41
|
+
* Expects an E.164-style number starting with "+" and a non-zero country code.
|
|
42
|
+
*/
|
|
43
|
+
const LIME_MARKETING_SMS_DESTINATION_REGEX = /^([+]+[1-9]{2})+\d{5,20}$/;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sender (FromNumber) regex from the /transactionsms/send schema — alphanumeric
|
|
47
|
+
* sender ID, 2–11 chars, Nordic letters + digits + _ - allowed, spaces allowed
|
|
48
|
+
* but not as first/last character:
|
|
49
|
+
* ^[a-zA-ZåäöÅÄÖæÆøØüÜ_\-\d][a-zA-ZåäöÅÄÖæÆøØüÜ_\-\d\s]{0,9}[a-zA-ZåäöÅÄÖæÆøØüÜ_\-\d]$
|
|
50
|
+
*/
|
|
51
|
+
const LIME_MARKETING_SMS_FROM_NUMBER_REGEX =
|
|
52
|
+
/^[a-zA-ZåäöÅÄÖæÆøØüÜ_\-\d][a-zA-ZåäöÅÄÖæÆøØüÜ_\-\d\s]{0,9}[a-zA-ZåäöÅÄÖæÆøØüÜ_\-\d]$/;
|
|
53
|
+
|
|
54
|
+
export function assertValidSmsDestinationNumber(
|
|
55
|
+
context: IExecuteFunctions,
|
|
56
|
+
value: string,
|
|
57
|
+
fieldLabel: string,
|
|
58
|
+
itemIndex: number
|
|
59
|
+
): void {
|
|
60
|
+
if (LIME_MARKETING_SMS_DESTINATION_REGEX.test(value)) return;
|
|
61
|
+
throw new NodeOperationError(
|
|
62
|
+
context.getNode(),
|
|
63
|
+
`${fieldLabel} is not a valid phone number: ${value}`,
|
|
64
|
+
{
|
|
65
|
+
itemIndex,
|
|
66
|
+
description:
|
|
67
|
+
'Expected an international format starting with "+", e.g. +46701234567.',
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function assertValidSmsFromNumber(
|
|
73
|
+
context: IExecuteFunctions,
|
|
74
|
+
value: string,
|
|
75
|
+
fieldLabel: string,
|
|
76
|
+
itemIndex: number
|
|
77
|
+
): void {
|
|
78
|
+
if (LIME_MARKETING_SMS_FROM_NUMBER_REGEX.test(value)) return;
|
|
79
|
+
throw new NodeOperationError(
|
|
80
|
+
context.getNode(),
|
|
81
|
+
`${fieldLabel} is not a valid sender ID: ${value}`,
|
|
82
|
+
{
|
|
83
|
+
itemIndex,
|
|
84
|
+
description:
|
|
85
|
+
'Expected 2–11 characters: letters, digits, underscores, hyphens, or spaces (spaces not allowed at start/end).',
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type LimeMarketingCredentials = {
|
|
91
|
+
url: string;
|
|
92
|
+
apiKey: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export async function getBaseUrl(
|
|
96
|
+
context: IAllExecuteFunctions
|
|
97
|
+
): Promise<string> {
|
|
98
|
+
const credentials = (await context.getCredentials(
|
|
99
|
+
LIME_MARKETING_API_CREDENTIAL_KEY
|
|
100
|
+
)) as unknown as LimeMarketingCredentials;
|
|
101
|
+
let url = credentials.url;
|
|
102
|
+
while (url.endsWith('/')) url = url.slice(0, -1);
|
|
103
|
+
return url;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Coerces empty/null values to undefined so the property is dropped from the
|
|
108
|
+
* outbound JSON payload rather than sent as "" or null.
|
|
109
|
+
*
|
|
110
|
+
* n8n's `collection` UI defaults to '' for string fields and null for number
|
|
111
|
+
* fields with `default: null`; we treat both as "user did not set this".
|
|
112
|
+
*
|
|
113
|
+
* @param value - The value to normalize.
|
|
114
|
+
*/
|
|
115
|
+
export function omitEmpty<T>(value: T | '' | null | undefined): T | undefined {
|
|
116
|
+
if (value === '' || value === null || value === undefined) return undefined;
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function buildAttachment(
|
|
121
|
+
context: IExecuteFunctions,
|
|
122
|
+
i: number,
|
|
123
|
+
binaryPropertyName: string,
|
|
124
|
+
fileNameOverride?: string
|
|
125
|
+
): Promise<AttachmentModel> {
|
|
126
|
+
const binaryData = context.helpers.assertBinaryData(i, binaryPropertyName);
|
|
127
|
+
const fileName = fileNameOverride || binaryData.fileName;
|
|
128
|
+
if (!fileName) {
|
|
129
|
+
throw new NodeOperationError(
|
|
130
|
+
context.getNode(),
|
|
131
|
+
`Attachment "${binaryPropertyName}" is missing a file name. Set one via "File Name Override" or ensure the upstream node sets it.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
FileData: binaryData.data,
|
|
136
|
+
FileNameWithExtension: fileName,
|
|
137
|
+
MimeType: binaryData.mimeType,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IDisplayOptions,
|
|
3
|
+
IExecuteFunctions,
|
|
4
|
+
INodeProperties,
|
|
5
|
+
NodeOperationError,
|
|
6
|
+
} from 'n8n-workflow';
|
|
7
|
+
import {
|
|
8
|
+
MERGE_CODES_INPUT_FIELDS,
|
|
9
|
+
MERGE_CODES_INPUT_JSON,
|
|
10
|
+
TagListInput,
|
|
11
|
+
TagModel,
|
|
12
|
+
} from '../models';
|
|
13
|
+
|
|
14
|
+
// Read the "Merge Codes (JSON)" parameter (already-parsed object or string)
|
|
15
|
+
// and turn it into TagModels. Throws NodeOperationError with item context on
|
|
16
|
+
// invalid JSON or non-object roots.
|
|
17
|
+
export function parseMergeCodesJson(
|
|
18
|
+
ctx: IExecuteFunctions,
|
|
19
|
+
i: number
|
|
20
|
+
): TagModel[] {
|
|
21
|
+
const jsonInput = ctx.getNodeParameter('mergeCodesJson', i) as
|
|
22
|
+
| string
|
|
23
|
+
| Record<string, string>;
|
|
24
|
+
let parsed: Record<string, unknown>;
|
|
25
|
+
try {
|
|
26
|
+
parsed =
|
|
27
|
+
typeof jsonInput === 'string' ? JSON.parse(jsonInput) : jsonInput;
|
|
28
|
+
} catch (error_) {
|
|
29
|
+
const message =
|
|
30
|
+
error_ instanceof Error ? error_.message : String(error_);
|
|
31
|
+
throw new NodeOperationError(
|
|
32
|
+
ctx.getNode(),
|
|
33
|
+
`Merge Codes (JSON) is not valid JSON: ${message}`,
|
|
34
|
+
{
|
|
35
|
+
itemIndex: i,
|
|
36
|
+
description:
|
|
37
|
+
'Enter a valid JSON object, for example: { "{{firstname}}": "John" }',
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
parsed === null ||
|
|
43
|
+
typeof parsed !== 'object' ||
|
|
44
|
+
Array.isArray(parsed)
|
|
45
|
+
) {
|
|
46
|
+
throw new NodeOperationError(
|
|
47
|
+
ctx.getNode(),
|
|
48
|
+
'Merge Codes (JSON) must be a JSON object of { mergeCode: value } pairs.',
|
|
49
|
+
{
|
|
50
|
+
itemIndex: i,
|
|
51
|
+
description:
|
|
52
|
+
'Example: { "{{firstname}}": "John", "{{custom}}": "value" }',
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
// Match the template-mapper path: send "" rather than the literal
|
|
57
|
+
// strings "null" / "undefined" when the user supplied either.
|
|
58
|
+
return Object.entries(parsed).map(([Name, Value]) => ({
|
|
59
|
+
Name,
|
|
60
|
+
Value: Value === null || Value === undefined ? '' : String(Value),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readFreeFormTagModels(
|
|
65
|
+
input: TagListInput | undefined
|
|
66
|
+
): TagModel[] {
|
|
67
|
+
return (input?.tag ?? []).map((t) => ({ Name: t.name, Value: t.value }));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Build the "Merge Codes Input Method" UI dropdown (Form vs JSON Object) for
|
|
71
|
+
// the given displayOptions.show selector. Reused by both email and SMS sends.
|
|
72
|
+
export function mergeCodesInputMethodProperty(
|
|
73
|
+
displayOptionsShow: NonNullable<IDisplayOptions['show']>
|
|
74
|
+
): INodeProperties {
|
|
75
|
+
return {
|
|
76
|
+
displayName: 'Merge Codes Input Method',
|
|
77
|
+
name: 'mergeCodesInputMethod',
|
|
78
|
+
type: 'options',
|
|
79
|
+
options: [
|
|
80
|
+
{
|
|
81
|
+
name: 'Form',
|
|
82
|
+
value: MERGE_CODES_INPUT_FIELDS,
|
|
83
|
+
description: 'Provide merge codes and values using the UI',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'JSON Object',
|
|
87
|
+
value: MERGE_CODES_INPUT_JSON,
|
|
88
|
+
description: 'Provide merge codes and values as a JSON object',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
default: MERGE_CODES_INPUT_FIELDS,
|
|
92
|
+
description:
|
|
93
|
+
'Form: fill in merge codes and values. JSON: provide them as an object.',
|
|
94
|
+
displayOptions: { show: displayOptionsShow },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const mergeCodesTagFieldOptions = [
|
|
99
|
+
{
|
|
100
|
+
name: 'tag',
|
|
101
|
+
displayName: 'Merge Code',
|
|
102
|
+
values: [
|
|
103
|
+
{
|
|
104
|
+
displayName: 'Name',
|
|
105
|
+
name: 'name',
|
|
106
|
+
type: 'string' as const,
|
|
107
|
+
default: '',
|
|
108
|
+
required: true,
|
|
109
|
+
placeholder: 'e.g. {{firstname}}',
|
|
110
|
+
description:
|
|
111
|
+
'The full merge code including braces, e.g. {{firstname}}.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
displayName: 'Value',
|
|
115
|
+
name: 'value',
|
|
116
|
+
type: 'string' as const,
|
|
117
|
+
default: '',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@limetech/n8n-nodes-lime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "n8n node to connect to Lime CRM",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Lime Technologies",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"main": "nodes/index.ts",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc -p tsconfig.build.json && copyfiles \"nodes/**/*.svg\" dist",
|
|
12
|
+
"build": "tsc -p tsconfig.build.json && copyfiles \"nodes/**/*.svg\" \"credentials/**/*.svg\" dist",
|
|
13
13
|
"dev": "tsc --watch",
|
|
14
14
|
"watch": "tsc --watch",
|
|
15
15
|
"lint": "eslint --max-warnings=0 .",
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
"n8nNodesApiVersion": 1,
|
|
35
35
|
"credentials": [
|
|
36
36
|
"dist/credentials/LimeCrmApi.credentials.js",
|
|
37
|
-
"dist/credentials/LimeFormsApi.credentials.js"
|
|
37
|
+
"dist/credentials/LimeFormsApi.credentials.js",
|
|
38
|
+
"dist/credentials/LimeMarketingApi.credentials.js"
|
|
38
39
|
],
|
|
39
40
|
"nodes": [
|
|
40
41
|
"dist/nodes/lime-crm/LimeCrmNode.node.js",
|
|
41
42
|
"dist/nodes/lime-crm/LimeCrmTrigger.node.js",
|
|
42
|
-
"dist/nodes/lime-forms/LimeFormsTrigger.node.js"
|
|
43
|
+
"dist/nodes/lime-forms/LimeFormsTrigger.node.js",
|
|
44
|
+
"dist/nodes/lime-marketing/LimeMarketing.node.js"
|
|
43
45
|
]
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|