n8n-nodes-imis 0.1.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/dist/credentials/ImisCrmApi.credentials.d.ts +7 -0
- package/dist/credentials/ImisCrmApi.credentials.js +42 -0
- package/dist/credentials/TdnWorkflowApi.credentials.d.ts +7 -0
- package/dist/credentials/TdnWorkflowApi.credentials.js +33 -0
- package/dist/nodes/ImisCrmIqa/3DN.png +0 -0
- package/dist/nodes/ImisCrmIqa/ImisCrmIqa.node.d.ts +5 -0
- package/dist/nodes/ImisCrmIqa/ImisCrmIqa.node.js +281 -0
- package/dist/nodes/ImisCrmIqa/helpers/ImisAuth.d.ts +17 -0
- package/dist/nodes/ImisCrmIqa/helpers/ImisAuth.js +88 -0
- package/dist/nodes/ImisCrmTrigger/ImisAuth.d.ts +17 -0
- package/dist/nodes/ImisCrmTrigger/ImisAuth.js +88 -0
- package/dist/nodes/ImisCrm_Unified/3DN.png +0 -0
- package/dist/nodes/ImisCrm_Unified/ImisCrm.node.d.ts +15 -0
- package/dist/nodes/ImisCrm_Unified/ImisCrm.node.js +1355 -0
- package/dist/nodes/ImisCrm_Unified/ImisCrmTrigger.node.d.ts +5 -0
- package/dist/nodes/ImisCrm_Unified/ImisCrmTrigger.node.js +509 -0
- package/dist/nodes/ImisCrm_Unified/ImisMemberProcess.node.d.ts +5 -0
- package/dist/nodes/ImisCrm_Unified/ImisMemberProcess.node.js +499 -0
- package/dist/nodes/ImisCrm_Unified/ImisSendEmail.node.d.ts +5 -0
- package/dist/nodes/ImisCrm_Unified/ImisSendEmail.node.js +228 -0
- package/dist/nodes/ImisCrm_Unified/helpers/ImisAuth.d.ts +17 -0
- package/dist/nodes/ImisCrm_Unified/helpers/ImisAuth.js +88 -0
- package/dist/nodes/ImisCrm_Unified/imis.png +0 -0
- package/dist/nodes/TdnWorkflowInput/3DN.png +0 -0
- package/dist/nodes/TdnWorkflowInput/TdnWorkflowInput.node.d.ts +10 -0
- package/dist/nodes/TdnWorkflowInput/TdnWorkflowInput.node.js +232 -0
- package/dist/nodes/TdnWorkflowTrigger/3DN.png +0 -0
- package/dist/nodes/TdnWorkflowTrigger/TdnWorkflowTrigger.node.d.ts +5 -0
- package/dist/nodes/TdnWorkflowTrigger/TdnWorkflowTrigger.node.js +99 -0
- package/dist/utils/CrmDeduplicationHelper.d.ts +30 -0
- package/dist/utils/CrmDeduplicationHelper.js +318 -0
- package/dist/utils/DeduplicationOptions.d.ts +5 -0
- package/dist/utils/DeduplicationOptions.js +63 -0
- package/dist/utils/DeduplicationStore.d.ts +37 -0
- package/dist/utils/DeduplicationStore.js +287 -0
- package/dist/utils/NodeDeduplicationHelper.d.ts +29 -0
- package/dist/utils/NodeDeduplicationHelper.js +153 -0
- package/index.js +2 -0
- package/package.json +64 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImisCrmApi = void 0;
|
|
4
|
+
class ImisCrmApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'imisCrmApi';
|
|
7
|
+
this.displayName = 'iMIS CRM API';
|
|
8
|
+
this.documentationUrl = 'https://developer.imis.com/';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Host URL',
|
|
12
|
+
name: 'hostUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
placeholder: 'https://your-imis-instance.com',
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'The base URL of your iMIS instance (without trailing slash)',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Username',
|
|
21
|
+
name: 'username',
|
|
22
|
+
type: 'string',
|
|
23
|
+
default: '',
|
|
24
|
+
required: true,
|
|
25
|
+
description: 'Your iMIS username',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
displayName: 'Password',
|
|
29
|
+
name: 'password',
|
|
30
|
+
type: 'string',
|
|
31
|
+
typeOptions: {
|
|
32
|
+
password: true,
|
|
33
|
+
},
|
|
34
|
+
default: '',
|
|
35
|
+
required: true,
|
|
36
|
+
description: 'Your iMIS password',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
// Note: Credential testing will be handled by the authentication flow in the node
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ImisCrmApi = ImisCrmApi;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TdnWorkflowApi = void 0;
|
|
4
|
+
class TdnWorkflowApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'tdnWorkflowApi';
|
|
7
|
+
this.displayName = '3DN Workflow API';
|
|
8
|
+
this.documentationUrl = '';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Base URL',
|
|
12
|
+
name: 'baseUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
placeholder: 'http://localhost:3000',
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'The base URL of your 3DN Workflow instance (without trailing slash)',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'API Key',
|
|
21
|
+
name: 'apiKey',
|
|
22
|
+
type: 'string',
|
|
23
|
+
typeOptions: {
|
|
24
|
+
password: true,
|
|
25
|
+
},
|
|
26
|
+
default: '',
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'Your 3DN Workflow API key',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.TdnWorkflowApi = TdnWorkflowApi;
|
|
Binary file
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImisCrmIqa = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const ImisAuth_1 = require("./helpers/ImisAuth");
|
|
6
|
+
function transformImisData(rawData) {
|
|
7
|
+
const cleanObject = (obj) => {
|
|
8
|
+
if (obj === null || obj === undefined) {
|
|
9
|
+
return obj;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(obj)) {
|
|
12
|
+
return obj.map(item => cleanObject(item));
|
|
13
|
+
}
|
|
14
|
+
if (typeof obj === 'object') {
|
|
15
|
+
const cleaned = {};
|
|
16
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
17
|
+
if (key === '$type') {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const cleanKey = key.startsWith('$') ? key.substring(1) : key;
|
|
21
|
+
cleaned[cleanKey] = cleanObject(value);
|
|
22
|
+
}
|
|
23
|
+
return cleaned;
|
|
24
|
+
}
|
|
25
|
+
return obj;
|
|
26
|
+
};
|
|
27
|
+
return cleanObject(rawData);
|
|
28
|
+
}
|
|
29
|
+
class ImisCrmIqa {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.description = {
|
|
32
|
+
displayName: 'iMIS IQA Query',
|
|
33
|
+
name: 'imisCrmIqa',
|
|
34
|
+
icon: 'file:3DN.png',
|
|
35
|
+
group: ['transform'],
|
|
36
|
+
version: 1,
|
|
37
|
+
description: 'Execute iMIS IQA queries and return iterable JSON results',
|
|
38
|
+
defaults: {
|
|
39
|
+
name: 'iMIS IQA Query',
|
|
40
|
+
},
|
|
41
|
+
inputs: ['main'],
|
|
42
|
+
outputs: ['main'],
|
|
43
|
+
credentials: [
|
|
44
|
+
{
|
|
45
|
+
name: 'imisCrmApi',
|
|
46
|
+
required: true,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
properties: [
|
|
50
|
+
{
|
|
51
|
+
displayName: 'Operation',
|
|
52
|
+
name: 'operation',
|
|
53
|
+
type: 'options',
|
|
54
|
+
noDataExpression: true,
|
|
55
|
+
options: [
|
|
56
|
+
{
|
|
57
|
+
name: 'Execute Query',
|
|
58
|
+
value: 'executeQuery',
|
|
59
|
+
description: 'Execute an IQA query and return results',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
default: 'executeQuery',
|
|
63
|
+
required: true,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
displayName: 'Query Name',
|
|
67
|
+
name: 'queryName',
|
|
68
|
+
type: 'string',
|
|
69
|
+
displayOptions: {
|
|
70
|
+
show: {
|
|
71
|
+
operation: ['executeQuery'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
default: '',
|
|
75
|
+
required: true,
|
|
76
|
+
description: 'The full path to the IQA query (e.g., $/Common/Queries/Value Lists/MemberTypeList)',
|
|
77
|
+
placeholder: '$/Common/Queries/Value Lists/MemberTypeList',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
displayName: 'Split Results',
|
|
81
|
+
name: 'splitResults',
|
|
82
|
+
type: 'boolean',
|
|
83
|
+
displayOptions: {
|
|
84
|
+
show: {
|
|
85
|
+
operation: ['executeQuery'],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
default: true,
|
|
89
|
+
description: 'Whether to split results into separate items (one per result row) or return all results as a single item',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
displayName: 'Additional Options',
|
|
93
|
+
name: 'additionalOptions',
|
|
94
|
+
type: 'collection',
|
|
95
|
+
placeholder: 'Add Option',
|
|
96
|
+
default: {},
|
|
97
|
+
displayOptions: {
|
|
98
|
+
show: {
|
|
99
|
+
operation: ['executeQuery'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
options: [
|
|
103
|
+
{
|
|
104
|
+
displayName: 'Limit',
|
|
105
|
+
name: 'limit',
|
|
106
|
+
type: 'number',
|
|
107
|
+
default: 100,
|
|
108
|
+
description: 'Maximum number of results to return',
|
|
109
|
+
typeOptions: {
|
|
110
|
+
minValue: 1,
|
|
111
|
+
maxValue: 10000,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
displayName: 'Offset',
|
|
116
|
+
name: 'offset',
|
|
117
|
+
type: 'number',
|
|
118
|
+
default: 0,
|
|
119
|
+
description: 'Number of results to skip (for pagination)',
|
|
120
|
+
typeOptions: {
|
|
121
|
+
minValue: 0,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
displayName: 'Query Parameters',
|
|
126
|
+
name: 'queryParameters',
|
|
127
|
+
type: 'fixedCollection',
|
|
128
|
+
typeOptions: {
|
|
129
|
+
multipleValues: true,
|
|
130
|
+
},
|
|
131
|
+
default: {},
|
|
132
|
+
description: 'Query parameters to pass to the IQA query',
|
|
133
|
+
options: [
|
|
134
|
+
{
|
|
135
|
+
name: 'parameter',
|
|
136
|
+
displayName: 'Parameter',
|
|
137
|
+
values: [
|
|
138
|
+
{
|
|
139
|
+
displayName: 'Name',
|
|
140
|
+
name: 'name',
|
|
141
|
+
type: 'string',
|
|
142
|
+
default: '',
|
|
143
|
+
description: 'Parameter name (for reference - iMIS uses positional parameters)',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
displayName: 'Value',
|
|
147
|
+
name: 'value',
|
|
148
|
+
type: 'string',
|
|
149
|
+
default: '',
|
|
150
|
+
description: 'Parameter value (must match order in IQA query definition)',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async execute() {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
const items = this.getInputData();
|
|
164
|
+
const returnData = [];
|
|
165
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
166
|
+
for (let i = 0; i < items.length; i++) {
|
|
167
|
+
try {
|
|
168
|
+
const credentials = await this.getCredentials('imisCrmApi');
|
|
169
|
+
const imisAuth = new ImisAuth_1.ImisAuth({
|
|
170
|
+
hostUrl: credentials.hostUrl,
|
|
171
|
+
username: credentials.username,
|
|
172
|
+
password: credentials.password,
|
|
173
|
+
});
|
|
174
|
+
if (operation === 'executeQuery') {
|
|
175
|
+
const queryName = this.getNodeParameter('queryName', i);
|
|
176
|
+
const splitResults = this.getNodeParameter('splitResults', i, true);
|
|
177
|
+
const additionalOptions = this.getNodeParameter('additionalOptions', i, {});
|
|
178
|
+
if (!queryName || queryName.trim() === '') {
|
|
179
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Query Name is required for item ${i}`);
|
|
180
|
+
}
|
|
181
|
+
// Build the query URL
|
|
182
|
+
let queryUrl = `${credentials.hostUrl}/api/IQA?QueryName=${encodeURIComponent(queryName.trim())}`;
|
|
183
|
+
// Add limit and offset if specified
|
|
184
|
+
if (additionalOptions.limit !== undefined) {
|
|
185
|
+
queryUrl += `&limit=${additionalOptions.limit}`;
|
|
186
|
+
}
|
|
187
|
+
if (additionalOptions.offset !== undefined) {
|
|
188
|
+
queryUrl += `&offset=${additionalOptions.offset}`;
|
|
189
|
+
}
|
|
190
|
+
// Add query parameters if specified
|
|
191
|
+
// iMIS IQA API requires parameters as &Parameter=value (repeated for each parameter in order)
|
|
192
|
+
// The parameter name is not included - only values, in the order defined in the IQA query
|
|
193
|
+
if ((_a = additionalOptions.queryParameters) === null || _a === void 0 ? void 0 : _a.parameter) {
|
|
194
|
+
const params = additionalOptions.queryParameters.parameter;
|
|
195
|
+
params.forEach(param => {
|
|
196
|
+
// Pass the value using &Parameter= format as required by iMIS IQA API
|
|
197
|
+
queryUrl += `&Parameter=${encodeURIComponent(param.value || '')}`;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Execute the query
|
|
201
|
+
const response = await imisAuth.makeAuthenticatedRequest(this.helpers.httpRequest.bind(this.helpers), {
|
|
202
|
+
method: 'GET',
|
|
203
|
+
url: queryUrl,
|
|
204
|
+
});
|
|
205
|
+
// Extract items from response
|
|
206
|
+
// IQA returns: { Items: { $values: [...] } } or { $values: [...] }
|
|
207
|
+
const items = ((_b = response.Items) === null || _b === void 0 ? void 0 : _b.$values) || response.$values || [];
|
|
208
|
+
if (!Array.isArray(items)) {
|
|
209
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Expected array of results from IQA query, got: ${typeof items}`);
|
|
210
|
+
}
|
|
211
|
+
// Transform the data
|
|
212
|
+
const cleanedItems = items.map((item) => {
|
|
213
|
+
var _a;
|
|
214
|
+
// IQA returns data in Properties collection
|
|
215
|
+
const properties = ((_a = item.Properties) === null || _a === void 0 ? void 0 : _a.$values) || [];
|
|
216
|
+
// Convert properties array to object
|
|
217
|
+
const dataObject = {};
|
|
218
|
+
if (Array.isArray(properties)) {
|
|
219
|
+
properties.forEach((prop) => {
|
|
220
|
+
const name = prop.Name || prop.name;
|
|
221
|
+
const value = prop.Value !== undefined ? prop.Value : prop.value;
|
|
222
|
+
if (name) {
|
|
223
|
+
dataObject[name] = value;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// If no properties found, return the transformed item as-is
|
|
228
|
+
if (Object.keys(dataObject).length === 0) {
|
|
229
|
+
return transformImisData(item);
|
|
230
|
+
}
|
|
231
|
+
return dataObject;
|
|
232
|
+
});
|
|
233
|
+
const metadata = {
|
|
234
|
+
operation,
|
|
235
|
+
queryName: queryName.trim(),
|
|
236
|
+
resultCount: cleanedItems.length,
|
|
237
|
+
executedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
if (splitResults) {
|
|
240
|
+
// Return each result as a separate item
|
|
241
|
+
cleanedItems.forEach((item, index) => {
|
|
242
|
+
returnData.push({
|
|
243
|
+
json: item,
|
|
244
|
+
meta: {
|
|
245
|
+
...metadata,
|
|
246
|
+
itemIndex: index,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Return all results as a single item
|
|
253
|
+
returnData.push({
|
|
254
|
+
json: {
|
|
255
|
+
results: cleanedItems,
|
|
256
|
+
metadata,
|
|
257
|
+
},
|
|
258
|
+
meta: metadata,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
if (this.continueOnFail()) {
|
|
265
|
+
returnData.push({
|
|
266
|
+
json: {
|
|
267
|
+
error: error.message,
|
|
268
|
+
},
|
|
269
|
+
meta: {
|
|
270
|
+
error: true,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return [returnData];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
exports.ImisCrmIqa = ImisCrmIqa;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IHttpRequestOptions } from 'n8n-workflow';
|
|
2
|
+
interface ImisCredentials {
|
|
3
|
+
hostUrl: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ImisAuth {
|
|
8
|
+
private accessToken;
|
|
9
|
+
private tokenExpiry;
|
|
10
|
+
private credentials;
|
|
11
|
+
constructor(credentials: ImisCredentials);
|
|
12
|
+
private requestNewToken;
|
|
13
|
+
private isTokenExpired;
|
|
14
|
+
getAccessToken(httpRequest: (options: IHttpRequestOptions) => Promise<any>): Promise<string>;
|
|
15
|
+
makeAuthenticatedRequest(httpRequest: (options: IHttpRequestOptions) => Promise<any>, requestOptions: IHttpRequestOptions): Promise<any>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImisAuth = void 0;
|
|
4
|
+
class ImisAuth {
|
|
5
|
+
constructor(credentials) {
|
|
6
|
+
this.accessToken = null;
|
|
7
|
+
this.tokenExpiry = null;
|
|
8
|
+
this.credentials = credentials;
|
|
9
|
+
}
|
|
10
|
+
async requestNewToken(httpRequest) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const formData = `grant_type=password&username=${encodeURIComponent(this.credentials.username)}&password=${encodeURIComponent(this.credentials.password)}`;
|
|
13
|
+
// Clean URL - remove trailing slash
|
|
14
|
+
const cleanUrl = this.credentials.hostUrl.replace(/\/$/, '');
|
|
15
|
+
try {
|
|
16
|
+
const response = await httpRequest({
|
|
17
|
+
method: 'POST',
|
|
18
|
+
url: `${cleanUrl}/token`,
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
21
|
+
},
|
|
22
|
+
body: formData,
|
|
23
|
+
});
|
|
24
|
+
if (!response.access_token) {
|
|
25
|
+
throw new Error('Failed to obtain access token from iMIS - no access_token in response');
|
|
26
|
+
}
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
|
31
|
+
throw new Error('Invalid iMIS credentials - check username and password');
|
|
32
|
+
}
|
|
33
|
+
else if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 404) {
|
|
34
|
+
throw new Error('iMIS token endpoint not found - check Host URL');
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Failed to obtain access token: ${error.message || 'Unknown error'}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
isTokenExpired() {
|
|
40
|
+
if (!this.tokenExpiry) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Add 5 minute buffer before actual expiry
|
|
44
|
+
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
45
|
+
return new Date().getTime() > (this.tokenExpiry.getTime() - bufferTime);
|
|
46
|
+
}
|
|
47
|
+
async getAccessToken(httpRequest) {
|
|
48
|
+
if (!this.accessToken || this.isTokenExpired()) {
|
|
49
|
+
const tokenResponse = await this.requestNewToken(httpRequest);
|
|
50
|
+
this.accessToken = tokenResponse.access_token;
|
|
51
|
+
// Calculate expiry time
|
|
52
|
+
const expiryTime = new Date();
|
|
53
|
+
expiryTime.setSeconds(expiryTime.getSeconds() + tokenResponse.expires_in);
|
|
54
|
+
this.tokenExpiry = expiryTime;
|
|
55
|
+
}
|
|
56
|
+
return this.accessToken;
|
|
57
|
+
}
|
|
58
|
+
async makeAuthenticatedRequest(httpRequest, requestOptions) {
|
|
59
|
+
var _a;
|
|
60
|
+
const token = await this.getAccessToken(httpRequest);
|
|
61
|
+
const authenticatedOptions = {
|
|
62
|
+
...requestOptions,
|
|
63
|
+
headers: {
|
|
64
|
+
...requestOptions.headers,
|
|
65
|
+
Authorization: `Bearer ${token}`,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
return await httpRequest(authenticatedOptions);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// If we get a 401, token might be invalid, try refreshing once
|
|
73
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
|
74
|
+
// Force token refresh
|
|
75
|
+
this.accessToken = null;
|
|
76
|
+
this.tokenExpiry = null;
|
|
77
|
+
const newToken = await this.getAccessToken(httpRequest);
|
|
78
|
+
authenticatedOptions.headers = {
|
|
79
|
+
...authenticatedOptions.headers,
|
|
80
|
+
Authorization: `Bearer ${newToken}`,
|
|
81
|
+
};
|
|
82
|
+
return await httpRequest(authenticatedOptions);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.ImisAuth = ImisAuth;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IHttpRequestOptions } from 'n8n-workflow';
|
|
2
|
+
interface ImisCredentials {
|
|
3
|
+
hostUrl: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ImisAuth {
|
|
8
|
+
private accessToken;
|
|
9
|
+
private tokenExpiry;
|
|
10
|
+
private credentials;
|
|
11
|
+
constructor(credentials: ImisCredentials);
|
|
12
|
+
private requestNewToken;
|
|
13
|
+
private isTokenExpired;
|
|
14
|
+
getAccessToken(httpRequest: (options: IHttpRequestOptions) => Promise<any>): Promise<string>;
|
|
15
|
+
makeAuthenticatedRequest(httpRequest: (options: IHttpRequestOptions) => Promise<any>, requestOptions: IHttpRequestOptions): Promise<any>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImisAuth = void 0;
|
|
4
|
+
class ImisAuth {
|
|
5
|
+
constructor(credentials) {
|
|
6
|
+
this.accessToken = null;
|
|
7
|
+
this.tokenExpiry = null;
|
|
8
|
+
this.credentials = credentials;
|
|
9
|
+
}
|
|
10
|
+
async requestNewToken(httpRequest) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const formData = `grant_type=password&username=${encodeURIComponent(this.credentials.username)}&password=${encodeURIComponent(this.credentials.password)}`;
|
|
13
|
+
// Clean URL - remove trailing slash
|
|
14
|
+
const cleanUrl = this.credentials.hostUrl.replace(/\/$/, '');
|
|
15
|
+
try {
|
|
16
|
+
const response = await httpRequest({
|
|
17
|
+
method: 'POST',
|
|
18
|
+
url: `${cleanUrl}/token`,
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
21
|
+
},
|
|
22
|
+
body: formData,
|
|
23
|
+
});
|
|
24
|
+
if (!response.access_token) {
|
|
25
|
+
throw new Error('Failed to obtain access token from iMIS - no access_token in response');
|
|
26
|
+
}
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
|
31
|
+
throw new Error('Invalid iMIS credentials - check username and password');
|
|
32
|
+
}
|
|
33
|
+
else if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 404) {
|
|
34
|
+
throw new Error('iMIS token endpoint not found - check Host URL');
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Failed to obtain access token: ${error.message || 'Unknown error'}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
isTokenExpired() {
|
|
40
|
+
if (!this.tokenExpiry) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Add 5 minute buffer before actual expiry
|
|
44
|
+
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
45
|
+
return new Date().getTime() > (this.tokenExpiry.getTime() - bufferTime);
|
|
46
|
+
}
|
|
47
|
+
async getAccessToken(httpRequest) {
|
|
48
|
+
if (!this.accessToken || this.isTokenExpired()) {
|
|
49
|
+
const tokenResponse = await this.requestNewToken(httpRequest);
|
|
50
|
+
this.accessToken = tokenResponse.access_token;
|
|
51
|
+
// Calculate expiry time
|
|
52
|
+
const expiryTime = new Date();
|
|
53
|
+
expiryTime.setSeconds(expiryTime.getSeconds() + tokenResponse.expires_in);
|
|
54
|
+
this.tokenExpiry = expiryTime;
|
|
55
|
+
}
|
|
56
|
+
return this.accessToken;
|
|
57
|
+
}
|
|
58
|
+
async makeAuthenticatedRequest(httpRequest, requestOptions) {
|
|
59
|
+
var _a;
|
|
60
|
+
const token = await this.getAccessToken(httpRequest);
|
|
61
|
+
const authenticatedOptions = {
|
|
62
|
+
...requestOptions,
|
|
63
|
+
headers: {
|
|
64
|
+
...requestOptions.headers,
|
|
65
|
+
Authorization: `Bearer ${token}`,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
return await httpRequest(authenticatedOptions);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// If we get a 401, token might be invalid, try refreshing once
|
|
73
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
|
74
|
+
// Force token refresh
|
|
75
|
+
this.accessToken = null;
|
|
76
|
+
this.tokenExpiry = null;
|
|
77
|
+
const newToken = await this.getAccessToken(httpRequest);
|
|
78
|
+
authenticatedOptions.headers = {
|
|
79
|
+
...authenticatedOptions.headers,
|
|
80
|
+
Authorization: `Bearer ${newToken}`,
|
|
81
|
+
};
|
|
82
|
+
return await httpRequest(authenticatedOptions);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.ImisAuth = ImisAuth;
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class ImisCrm implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
methods: {
|
|
5
|
+
loadOptions: {
|
|
6
|
+
getSalutations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
|
+
getGenders(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
8
|
+
getMemberStatuses(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
9
|
+
getCategories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
10
|
+
getMemberTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
11
|
+
getActivityTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
15
|
+
}
|