@unito/integration-sdk 0.1.4 → 0.1.6
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/src/handler.d.ts +1 -1
- package/dist/src/handler.js +10 -10
- package/dist/src/integration.d.ts +6 -1
- package/dist/src/integration.js +5 -2
- package/dist/src/middlewares/logger.js +2 -2
- package/dist/src/resources/provider.d.ts +4 -0
- package/dist/src/resources/provider.js +1 -1
- package/dist/test/integration.test.d.ts +1 -0
- package/dist/test/integration.test.js +26 -0
- package/dist/test/middlewares/filters.test.js +8 -0
- package/dist/test/middlewares/logger.test.js +2 -2
- package/dist/test/resources/provider.test.js +11 -0
- package/package.json +1 -1
- package/src/handler.ts +11 -11
- package/src/integration.ts +10 -4
- package/src/middlewares/logger.ts +2 -2
- package/src/resources/provider.ts +5 -3
- package/test/integration.test.ts +34 -0
- package/test/middlewares/filters.test.ts +17 -0
- package/test/middlewares/logger.test.ts +3 -5
- package/test/resources/provider.test.ts +12 -0
package/dist/src/handler.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ type WebhookSubscriptionHandlers = {
|
|
|
54
54
|
updateWebhookSubscriptions: UpdateWebhookSubscriptionsHandler;
|
|
55
55
|
};
|
|
56
56
|
type AcknowledgeWebhookHandlers = {
|
|
57
|
-
acknowledgeWebhooks
|
|
57
|
+
acknowledgeWebhooks: AckknowledgeWebhooksHandler;
|
|
58
58
|
};
|
|
59
59
|
export type HandlersInput = ItemHandlers | CredentialAccountHandlers | ParseWebhookHandlers | WebhookSubscriptionHandlers | AcknowledgeWebhookHandlers;
|
|
60
60
|
export declare class Handler {
|
package/dist/src/handler.js
CHANGED
|
@@ -16,7 +16,7 @@ function assertValidConfiguration(path, pathWithIdentifier, handlers) {
|
|
|
16
16
|
const hasIndividualHandlers = individualHandlers.some(handler => handler in handlers);
|
|
17
17
|
const hasCollectionHandlers = collectionHandlers.some(handler => handler in handlers);
|
|
18
18
|
if (hasIndividualHandlers && hasCollectionHandlers) {
|
|
19
|
-
throw new InvalidHandler(`The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both
|
|
19
|
+
throw new InvalidHandler(`The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both.`);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -83,7 +83,7 @@ export class Handler {
|
|
|
83
83
|
console.debug(`\x1b[33mMounting handler at path ${this.pathWithIdentifier}`);
|
|
84
84
|
if (this.handlers.getCollection) {
|
|
85
85
|
const handler = this.handlers.getCollection;
|
|
86
|
-
console.debug(` Enabling GET ${this.path}`);
|
|
86
|
+
console.debug(` Enabling getCollection at GET ${this.path}`);
|
|
87
87
|
router.get(this.path, async (req, res) => {
|
|
88
88
|
if (!res.locals.credentials) {
|
|
89
89
|
throw new UnauthorizedError();
|
|
@@ -101,7 +101,7 @@ export class Handler {
|
|
|
101
101
|
}
|
|
102
102
|
if (this.handlers.createItem) {
|
|
103
103
|
const handler = this.handlers.createItem;
|
|
104
|
-
console.debug(` Enabling POST ${this.path}`);
|
|
104
|
+
console.debug(` Enabling createItem at POST ${this.path}`);
|
|
105
105
|
router.post(this.path, async (req, res) => {
|
|
106
106
|
if (!res.locals.credentials) {
|
|
107
107
|
throw new UnauthorizedError();
|
|
@@ -119,7 +119,7 @@ export class Handler {
|
|
|
119
119
|
}
|
|
120
120
|
if (this.handlers.getItem) {
|
|
121
121
|
const handler = this.handlers.getItem;
|
|
122
|
-
console.debug(` Enabling GET ${this.pathWithIdentifier}`);
|
|
122
|
+
console.debug(` Enabling getItem at GET ${this.pathWithIdentifier}`);
|
|
123
123
|
router.get(this.pathWithIdentifier, async (req, res) => {
|
|
124
124
|
if (!res.locals.credentials) {
|
|
125
125
|
throw new UnauthorizedError();
|
|
@@ -135,7 +135,7 @@ export class Handler {
|
|
|
135
135
|
}
|
|
136
136
|
if (this.handlers.updateItem) {
|
|
137
137
|
const handler = this.handlers.updateItem;
|
|
138
|
-
console.debug(` Enabling PATCH ${this.pathWithIdentifier}`);
|
|
138
|
+
console.debug(` Enabling updateItem at PATCH ${this.pathWithIdentifier}`);
|
|
139
139
|
router.patch(this.pathWithIdentifier, async (req, res) => {
|
|
140
140
|
if (!res.locals.credentials) {
|
|
141
141
|
throw new UnauthorizedError();
|
|
@@ -153,7 +153,7 @@ export class Handler {
|
|
|
153
153
|
}
|
|
154
154
|
if (this.handlers.deleteItem) {
|
|
155
155
|
const handler = this.handlers.deleteItem;
|
|
156
|
-
console.debug(` Enabling DELETE ${this.pathWithIdentifier}`);
|
|
156
|
+
console.debug(` Enabling deleteItem at DELETE ${this.pathWithIdentifier}`);
|
|
157
157
|
router.delete(this.pathWithIdentifier, async (req, res) => {
|
|
158
158
|
if (!res.locals.credentials) {
|
|
159
159
|
throw new UnauthorizedError();
|
|
@@ -169,7 +169,7 @@ export class Handler {
|
|
|
169
169
|
}
|
|
170
170
|
if (this.handlers.getCredentialAccount) {
|
|
171
171
|
const handler = this.handlers.getCredentialAccount;
|
|
172
|
-
console.debug(` Enabling GET ${this.pathWithIdentifier}`);
|
|
172
|
+
console.debug(` Enabling getCredentialAccount at GET ${this.pathWithIdentifier}`);
|
|
173
173
|
router.get(this.pathWithIdentifier, async (req, res) => {
|
|
174
174
|
if (!res.locals.credentials) {
|
|
175
175
|
throw new UnauthorizedError();
|
|
@@ -185,7 +185,7 @@ export class Handler {
|
|
|
185
185
|
}
|
|
186
186
|
if (this.handlers.acknowledgeWebhooks) {
|
|
187
187
|
const handler = this.handlers.acknowledgeWebhooks;
|
|
188
|
-
console.debug(` Enabling POST ${this.pathWithIdentifier}`);
|
|
188
|
+
console.debug(` Enabling acknowledgeWebhooks at POST ${this.pathWithIdentifier}`);
|
|
189
189
|
router.post(this.pathWithIdentifier, async (req, res) => {
|
|
190
190
|
assertWebhookParseRequestPayload(req.body);
|
|
191
191
|
const response = await handler({
|
|
@@ -199,7 +199,7 @@ export class Handler {
|
|
|
199
199
|
}
|
|
200
200
|
if (this.handlers.parseWebhooks) {
|
|
201
201
|
const handler = this.handlers.parseWebhooks;
|
|
202
|
-
console.debug(` Enabling POST ${this.pathWithIdentifier}`);
|
|
202
|
+
console.debug(` Enabling parseWebhooks at POST ${this.pathWithIdentifier}`);
|
|
203
203
|
router.post(this.pathWithIdentifier, async (req, res) => {
|
|
204
204
|
assertWebhookParseRequestPayload(req.body);
|
|
205
205
|
const response = await handler({
|
|
@@ -213,7 +213,7 @@ export class Handler {
|
|
|
213
213
|
}
|
|
214
214
|
if (this.handlers.updateWebhookSubscriptions) {
|
|
215
215
|
const handler = this.handlers.updateWebhookSubscriptions;
|
|
216
|
-
console.debug(` Enabling PUT ${this.pathWithIdentifier}`);
|
|
216
|
+
console.debug(` Enabling updateWebhookSubscriptions at PUT ${this.pathWithIdentifier}`);
|
|
217
217
|
router.put(this.pathWithIdentifier, async (req, res) => {
|
|
218
218
|
if (!res.locals.credentials) {
|
|
219
219
|
throw new UnauthorizedError();
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { HandlersInput } from './handler.js';
|
|
2
|
+
type Options = {
|
|
3
|
+
port?: number;
|
|
4
|
+
};
|
|
2
5
|
export default class Integration {
|
|
3
6
|
private handlers;
|
|
4
7
|
private instance;
|
|
5
8
|
private cache;
|
|
6
|
-
|
|
9
|
+
private port;
|
|
10
|
+
constructor(options?: Options);
|
|
7
11
|
addHandler(path: string, handlers: HandlersInput): void;
|
|
8
12
|
start(): void;
|
|
9
13
|
}
|
|
14
|
+
export {};
|
package/dist/src/integration.js
CHANGED
|
@@ -18,7 +18,9 @@ export default class Integration {
|
|
|
18
18
|
handlers;
|
|
19
19
|
instance = undefined;
|
|
20
20
|
cache = undefined;
|
|
21
|
-
|
|
21
|
+
port;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.port = options.port || 9200;
|
|
22
24
|
this.handlers = [];
|
|
23
25
|
}
|
|
24
26
|
addHandler(path, handlers) {
|
|
@@ -85,7 +87,8 @@ export default class Integration {
|
|
|
85
87
|
app.use(errorsMiddleware);
|
|
86
88
|
// Must be the last handler.
|
|
87
89
|
app.use(notFoundMiddleware);
|
|
88
|
-
|
|
90
|
+
// Start the server.
|
|
91
|
+
this.instance = app.listen(this.port, () => console.info(`Server started on port ${this.port}.`));
|
|
89
92
|
// Trap exit signals.
|
|
90
93
|
['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
|
|
91
94
|
process.once(signalType, async () => {
|
|
@@ -6,11 +6,11 @@ const middleware = (req, res, next) => {
|
|
|
6
6
|
const rawAdditionalContext = req.header(ADDITIONAL_CONTEXT_HEADER);
|
|
7
7
|
if (typeof rawAdditionalContext === 'string') {
|
|
8
8
|
try {
|
|
9
|
-
const additionalContext = JSON.parse(
|
|
9
|
+
const additionalContext = JSON.parse(rawAdditionalContext);
|
|
10
10
|
logger.decorate(additionalContext);
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
13
|
-
logger.warn(`
|
|
13
|
+
logger.warn(`Failed parsing header ${ADDITIONAL_CONTEXT_HEADER}: ${rawAdditionalContext}`);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
next();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Credentials } from '../middlewares/credentials.js';
|
|
2
|
+
import Logger from '../resources/logger.js';
|
|
2
3
|
/**
|
|
3
4
|
* RateLimiter is a wrapper function that you can provide to limit the rate of calls to the provider based on the
|
|
4
5
|
* caller's credentials.
|
|
@@ -17,9 +18,11 @@ import { Credentials } from '../middlewares/credentials.js';
|
|
|
17
18
|
*/
|
|
18
19
|
export type RateLimiter = <T>(context: {
|
|
19
20
|
credentials: Credentials;
|
|
21
|
+
logger: Logger;
|
|
20
22
|
}, targetFunction: () => Promise<Response<T>>) => Promise<Response<T>>;
|
|
21
23
|
export interface RequestOptions {
|
|
22
24
|
credentials: Credentials;
|
|
25
|
+
logger: Logger;
|
|
23
26
|
queryParams?: {
|
|
24
27
|
[key: string]: string;
|
|
25
28
|
};
|
|
@@ -36,6 +39,7 @@ export declare class Provider {
|
|
|
36
39
|
protected rateLimiter: RateLimiter | undefined;
|
|
37
40
|
protected prepareRequest: (context: {
|
|
38
41
|
credentials: Credentials;
|
|
42
|
+
logger: Logger;
|
|
39
43
|
}) => {
|
|
40
44
|
url: string;
|
|
41
45
|
headers: Record<string, string>;
|
|
@@ -63,7 +63,7 @@ export class Provider {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
async fetchWrapper(endpoint, body, options) {
|
|
66
|
-
const { url: providerUrl, headers: providerHeaders } = this.prepareRequest(
|
|
66
|
+
const { url: providerUrl, headers: providerHeaders } = this.prepareRequest(options);
|
|
67
67
|
let absoluteUrl = [providerUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
|
|
68
68
|
if (options.queryParams) {
|
|
69
69
|
absoluteUrl = `${absoluteUrl}?${new URLSearchParams(options.queryParams)}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import Integration from '../src/integration.js';
|
|
4
|
+
describe('Integration', () => {
|
|
5
|
+
describe('constructor', () => {
|
|
6
|
+
it('defaults to port 9200', () => {
|
|
7
|
+
const integration = new Integration();
|
|
8
|
+
assert.equal(integration['port'], 9200);
|
|
9
|
+
});
|
|
10
|
+
it('accepts an optional port', () => {
|
|
11
|
+
const integration = new Integration({ port: 1234 });
|
|
12
|
+
assert.equal(integration['port'], 1234);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('addHandler', () => {
|
|
16
|
+
it('works', () => {
|
|
17
|
+
const integration = new Integration();
|
|
18
|
+
integration.addHandler('/', {});
|
|
19
|
+
const handler = integration['handlers'][0];
|
|
20
|
+
assert.ok(handler);
|
|
21
|
+
assert.equal(handler['path'], '/');
|
|
22
|
+
assert.equal(handler['pathWithIdentifier'], '/');
|
|
23
|
+
assert.deepEqual(handler['handlers'], {});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -15,6 +15,14 @@ describe('filters middleware', () => {
|
|
|
15
15
|
],
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
|
+
it('decodes URI components', () => {
|
|
19
|
+
const request = { query: { filter: 'status=foo%2Cbar!!%2C%3Fbaz%3D!%3Equx' } };
|
|
20
|
+
const response = { locals: {} };
|
|
21
|
+
middleware(request, response, () => { });
|
|
22
|
+
assert.deepEqual(response.locals, {
|
|
23
|
+
filters: [{ field: 'status', operator: OperatorType.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
|
|
24
|
+
});
|
|
25
|
+
});
|
|
18
26
|
it('no data', () => {
|
|
19
27
|
const request = { query: {} };
|
|
20
28
|
const response = { locals: {} };
|
|
@@ -19,9 +19,9 @@ describe('logger middleware', () => {
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
it('additional context', () => {
|
|
22
|
-
const additional =
|
|
22
|
+
const additional = JSON.stringify({
|
|
23
23
|
foo: 'bar',
|
|
24
|
-
})
|
|
24
|
+
});
|
|
25
25
|
const request = { header: (_key) => additional };
|
|
26
26
|
const response = { locals: { correlationId: '123' } };
|
|
27
27
|
middleware(request, response, () => { });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
3
|
import { Provider } from '../../src/resources/provider.js';
|
|
4
|
+
import Logger from '../../src/resources/logger.js';
|
|
4
5
|
describe('Provider', () => {
|
|
5
6
|
const provider = new Provider({
|
|
6
7
|
prepareRequest: requestOptions => {
|
|
@@ -13,6 +14,7 @@ describe('Provider', () => {
|
|
|
13
14
|
};
|
|
14
15
|
},
|
|
15
16
|
});
|
|
17
|
+
const logger = new Logger();
|
|
16
18
|
it('get', async (context) => {
|
|
17
19
|
const response = new Response('{"data": "value"}', {
|
|
18
20
|
status: 200,
|
|
@@ -21,6 +23,7 @@ describe('Provider', () => {
|
|
|
21
23
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
22
24
|
const actualResponse = await provider.get('/endpoint', {
|
|
23
25
|
credentials: { apiKey: 'apikey#1111' },
|
|
26
|
+
logger: logger,
|
|
24
27
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
25
28
|
});
|
|
26
29
|
assert.equal(fetchMock.mock.calls.length, 1);
|
|
@@ -50,6 +53,7 @@ describe('Provider', () => {
|
|
|
50
53
|
data: 'createdItemInfo',
|
|
51
54
|
}, {
|
|
52
55
|
credentials: { apiKey: 'apikey#1111' },
|
|
56
|
+
logger: logger,
|
|
53
57
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
54
58
|
});
|
|
55
59
|
assert.equal(fetchMock.mock.calls.length, 1);
|
|
@@ -80,6 +84,7 @@ describe('Provider', () => {
|
|
|
80
84
|
data: 'updatedItemInfo',
|
|
81
85
|
}, {
|
|
82
86
|
credentials: { apiKey: 'apikey#1111' },
|
|
87
|
+
logger: logger,
|
|
83
88
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
84
89
|
});
|
|
85
90
|
assert.equal(fetchMock.mock.calls.length, 1);
|
|
@@ -109,6 +114,7 @@ describe('Provider', () => {
|
|
|
109
114
|
data: 'updatedItemInfo',
|
|
110
115
|
}, {
|
|
111
116
|
credentials: { apiKey: 'apikey#1111' },
|
|
117
|
+
logger: logger,
|
|
112
118
|
queryParams: { param1: 'value1', param2: 'value2' },
|
|
113
119
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
114
120
|
});
|
|
@@ -137,6 +143,7 @@ describe('Provider', () => {
|
|
|
137
143
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
138
144
|
const actualResponse = await provider.delete('/endpoint/123', {
|
|
139
145
|
credentials: { apiKey: 'apikey#1111' },
|
|
146
|
+
logger: logger,
|
|
140
147
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
141
148
|
});
|
|
142
149
|
assert.equal(fetchMock.mock.calls.length, 1);
|
|
@@ -177,6 +184,7 @@ describe('Provider', () => {
|
|
|
177
184
|
const fetchMock = context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
178
185
|
const options = {
|
|
179
186
|
credentials: { apiKey: 'apikey#1111' },
|
|
187
|
+
logger: logger,
|
|
180
188
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
181
189
|
};
|
|
182
190
|
const actualResponse = await rateLimitedProvider.delete('/endpoint/123', options);
|
|
@@ -206,6 +214,7 @@ describe('Provider', () => {
|
|
|
206
214
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
207
215
|
assert.rejects(() => provider.get('/endpoint/123', {
|
|
208
216
|
credentials: { apiKey: 'apikey#1111' },
|
|
217
|
+
logger: logger,
|
|
209
218
|
}));
|
|
210
219
|
});
|
|
211
220
|
it('throws on status 400', async (context) => {
|
|
@@ -215,6 +224,7 @@ describe('Provider', () => {
|
|
|
215
224
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
216
225
|
assert.rejects(() => provider.get('/endpoint/123', {
|
|
217
226
|
credentials: { apiKey: 'apikey#1111' },
|
|
227
|
+
logger: logger,
|
|
218
228
|
}));
|
|
219
229
|
});
|
|
220
230
|
it('throws on status 429', async (context) => {
|
|
@@ -224,6 +234,7 @@ describe('Provider', () => {
|
|
|
224
234
|
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
225
235
|
assert.rejects(() => provider.get('/endpoint/123', {
|
|
226
236
|
credentials: { apiKey: 'apikey#1111' },
|
|
237
|
+
logger: logger,
|
|
227
238
|
}));
|
|
228
239
|
});
|
|
229
240
|
});
|
package/package.json
CHANGED
package/src/handler.ts
CHANGED
|
@@ -88,7 +88,7 @@ type WebhookSubscriptionHandlers = {
|
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
type AcknowledgeWebhookHandlers = {
|
|
91
|
-
acknowledgeWebhooks
|
|
91
|
+
acknowledgeWebhooks: AckknowledgeWebhooksHandler;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
export type HandlersInput =
|
|
@@ -128,7 +128,7 @@ function assertValidConfiguration(path: Path, pathWithIdentifier: Path, handlers
|
|
|
128
128
|
|
|
129
129
|
if (hasIndividualHandlers && hasCollectionHandlers) {
|
|
130
130
|
throw new InvalidHandler(
|
|
131
|
-
`The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both
|
|
131
|
+
`The provided path '${path}' doesn't differentiate between individual and collection level operation, so you cannot define both.`,
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -221,7 +221,7 @@ export class Handler {
|
|
|
221
221
|
if (this.handlers.getCollection) {
|
|
222
222
|
const handler = this.handlers.getCollection;
|
|
223
223
|
|
|
224
|
-
console.debug(` Enabling GET ${this.path}`);
|
|
224
|
+
console.debug(` Enabling getCollection at GET ${this.path}`);
|
|
225
225
|
|
|
226
226
|
router.get(this.path, async (req, res) => {
|
|
227
227
|
if (!res.locals.credentials) {
|
|
@@ -244,7 +244,7 @@ export class Handler {
|
|
|
244
244
|
if (this.handlers.createItem) {
|
|
245
245
|
const handler = this.handlers.createItem;
|
|
246
246
|
|
|
247
|
-
console.debug(` Enabling POST ${this.path}`);
|
|
247
|
+
console.debug(` Enabling createItem at POST ${this.path}`);
|
|
248
248
|
|
|
249
249
|
router.post(this.path, async (req, res) => {
|
|
250
250
|
if (!res.locals.credentials) {
|
|
@@ -268,7 +268,7 @@ export class Handler {
|
|
|
268
268
|
if (this.handlers.getItem) {
|
|
269
269
|
const handler = this.handlers.getItem;
|
|
270
270
|
|
|
271
|
-
console.debug(` Enabling GET ${this.pathWithIdentifier}`);
|
|
271
|
+
console.debug(` Enabling getItem at GET ${this.pathWithIdentifier}`);
|
|
272
272
|
|
|
273
273
|
router.get(this.pathWithIdentifier, async (req, res) => {
|
|
274
274
|
if (!res.locals.credentials) {
|
|
@@ -289,7 +289,7 @@ export class Handler {
|
|
|
289
289
|
if (this.handlers.updateItem) {
|
|
290
290
|
const handler = this.handlers.updateItem;
|
|
291
291
|
|
|
292
|
-
console.debug(` Enabling PATCH ${this.pathWithIdentifier}`);
|
|
292
|
+
console.debug(` Enabling updateItem at PATCH ${this.pathWithIdentifier}`);
|
|
293
293
|
|
|
294
294
|
router.patch(this.pathWithIdentifier, async (req, res) => {
|
|
295
295
|
if (!res.locals.credentials) {
|
|
@@ -313,7 +313,7 @@ export class Handler {
|
|
|
313
313
|
if (this.handlers.deleteItem) {
|
|
314
314
|
const handler = this.handlers.deleteItem;
|
|
315
315
|
|
|
316
|
-
console.debug(` Enabling DELETE ${this.pathWithIdentifier}`);
|
|
316
|
+
console.debug(` Enabling deleteItem at DELETE ${this.pathWithIdentifier}`);
|
|
317
317
|
|
|
318
318
|
router.delete(this.pathWithIdentifier, async (req, res) => {
|
|
319
319
|
if (!res.locals.credentials) {
|
|
@@ -334,7 +334,7 @@ export class Handler {
|
|
|
334
334
|
if (this.handlers.getCredentialAccount) {
|
|
335
335
|
const handler = this.handlers.getCredentialAccount;
|
|
336
336
|
|
|
337
|
-
console.debug(` Enabling GET ${this.pathWithIdentifier}`);
|
|
337
|
+
console.debug(` Enabling getCredentialAccount at GET ${this.pathWithIdentifier}`);
|
|
338
338
|
|
|
339
339
|
router.get(this.pathWithIdentifier, async (req, res) => {
|
|
340
340
|
if (!res.locals.credentials) {
|
|
@@ -355,7 +355,7 @@ export class Handler {
|
|
|
355
355
|
if (this.handlers.acknowledgeWebhooks) {
|
|
356
356
|
const handler = this.handlers.acknowledgeWebhooks;
|
|
357
357
|
|
|
358
|
-
console.debug(` Enabling POST ${this.pathWithIdentifier}`);
|
|
358
|
+
console.debug(` Enabling acknowledgeWebhooks at POST ${this.pathWithIdentifier}`);
|
|
359
359
|
|
|
360
360
|
router.post(this.pathWithIdentifier, async (req, res) => {
|
|
361
361
|
assertWebhookParseRequestPayload(req.body);
|
|
@@ -374,7 +374,7 @@ export class Handler {
|
|
|
374
374
|
if (this.handlers.parseWebhooks) {
|
|
375
375
|
const handler = this.handlers.parseWebhooks;
|
|
376
376
|
|
|
377
|
-
console.debug(` Enabling POST ${this.pathWithIdentifier}`);
|
|
377
|
+
console.debug(` Enabling parseWebhooks at POST ${this.pathWithIdentifier}`);
|
|
378
378
|
|
|
379
379
|
router.post(this.pathWithIdentifier, async (req, res) => {
|
|
380
380
|
assertWebhookParseRequestPayload(req.body);
|
|
@@ -393,7 +393,7 @@ export class Handler {
|
|
|
393
393
|
if (this.handlers.updateWebhookSubscriptions) {
|
|
394
394
|
const handler = this.handlers.updateWebhookSubscriptions;
|
|
395
395
|
|
|
396
|
-
console.debug(` Enabling PUT ${this.pathWithIdentifier}`);
|
|
396
|
+
console.debug(` Enabling updateWebhookSubscriptions at PUT ${this.pathWithIdentifier}`);
|
|
397
397
|
|
|
398
398
|
router.put(this.pathWithIdentifier, async (req, res) => {
|
|
399
399
|
if (!res.locals.credentials) {
|
package/src/integration.ts
CHANGED
|
@@ -18,6 +18,10 @@ function printErrorMessage(message: string) {
|
|
|
18
18
|
console.error(message);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
type Options = {
|
|
22
|
+
port?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
21
25
|
export default class Integration {
|
|
22
26
|
private handlers: Handler[];
|
|
23
27
|
|
|
@@ -25,7 +29,10 @@ export default class Integration {
|
|
|
25
29
|
|
|
26
30
|
private cache: Cache | undefined = undefined;
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
private port: number;
|
|
33
|
+
|
|
34
|
+
constructor(options: Options = {}) {
|
|
35
|
+
this.port = options.port || 9200;
|
|
29
36
|
this.handlers = [];
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -101,9 +108,8 @@ export default class Integration {
|
|
|
101
108
|
// Must be the last handler.
|
|
102
109
|
app.use(notFoundMiddleware);
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
111
|
+
// Start the server.
|
|
112
|
+
this.instance = app.listen(this.port, () => console.info(`Server started on port ${this.port}.`));
|
|
107
113
|
|
|
108
114
|
// Trap exit signals.
|
|
109
115
|
['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
|
|
@@ -22,11 +22,11 @@ const middleware = (req: Request, res: Response, next: NextFunction) => {
|
|
|
22
22
|
|
|
23
23
|
if (typeof rawAdditionalContext === 'string') {
|
|
24
24
|
try {
|
|
25
|
-
const additionalContext = JSON.parse(
|
|
25
|
+
const additionalContext = JSON.parse(rawAdditionalContext);
|
|
26
26
|
|
|
27
27
|
logger.decorate(additionalContext);
|
|
28
28
|
} catch (error) {
|
|
29
|
-
logger.warn(`
|
|
29
|
+
logger.warn(`Failed parsing header ${ADDITIONAL_CONTEXT_HEADER}: ${rawAdditionalContext}`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildHttpError } from '../errors.js';
|
|
2
2
|
import { Credentials } from '../middlewares/credentials.js';
|
|
3
|
+
import Logger from '../resources/logger.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* RateLimiter is a wrapper function that you can provide to limit the rate of calls to the provider based on the
|
|
@@ -18,12 +19,13 @@ import { Credentials } from '../middlewares/credentials.js';
|
|
|
18
19
|
* @throws HttpError when the provider returns an error.
|
|
19
20
|
*/
|
|
20
21
|
export type RateLimiter = <T>(
|
|
21
|
-
context: { credentials: Credentials },
|
|
22
|
+
context: { credentials: Credentials; logger: Logger },
|
|
22
23
|
targetFunction: () => Promise<Response<T>>,
|
|
23
24
|
) => Promise<Response<T>>;
|
|
24
25
|
|
|
25
26
|
export interface RequestOptions {
|
|
26
27
|
credentials: Credentials;
|
|
28
|
+
logger: Logger;
|
|
27
29
|
queryParams?: { [key: string]: string };
|
|
28
30
|
additionnalheaders?: { [key: string]: string };
|
|
29
31
|
}
|
|
@@ -36,7 +38,7 @@ export interface Response<T> {
|
|
|
36
38
|
|
|
37
39
|
export class Provider {
|
|
38
40
|
protected rateLimiter: RateLimiter | undefined = undefined;
|
|
39
|
-
protected prepareRequest: (context: { credentials: Credentials }) => {
|
|
41
|
+
protected prepareRequest: (context: { credentials: Credentials; logger: Logger }) => {
|
|
40
42
|
url: string;
|
|
41
43
|
headers: Record<string, string>;
|
|
42
44
|
};
|
|
@@ -116,7 +118,7 @@ export class Provider {
|
|
|
116
118
|
body: Record<string, unknown> | null,
|
|
117
119
|
options: RequestOptions & { defaultHeaders: { 'Content-Type': string; Accept: string }; method: string },
|
|
118
120
|
): Promise<Response<T>> {
|
|
119
|
-
const { url: providerUrl, headers: providerHeaders } = this.prepareRequest(
|
|
121
|
+
const { url: providerUrl, headers: providerHeaders } = this.prepareRequest(options);
|
|
120
122
|
|
|
121
123
|
let absoluteUrl = [providerUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
|
|
122
124
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import Integration from '../src/integration.js';
|
|
4
|
+
|
|
5
|
+
describe('Integration', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('defaults to port 9200', () => {
|
|
8
|
+
const integration = new Integration();
|
|
9
|
+
|
|
10
|
+
assert.equal(integration['port'], 9200);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('accepts an optional port', () => {
|
|
14
|
+
const integration = new Integration({ port: 1234 });
|
|
15
|
+
|
|
16
|
+
assert.equal(integration['port'], 1234);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('addHandler', () => {
|
|
21
|
+
it('works', () => {
|
|
22
|
+
const integration = new Integration();
|
|
23
|
+
|
|
24
|
+
integration.addHandler('/', {});
|
|
25
|
+
|
|
26
|
+
const handler = integration['handlers'][0];
|
|
27
|
+
|
|
28
|
+
assert.ok(handler);
|
|
29
|
+
assert.equal(handler['path'], '/');
|
|
30
|
+
assert.equal(handler['pathWithIdentifier'], '/');
|
|
31
|
+
assert.deepEqual(handler['handlers'], {});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -26,6 +26,23 @@ describe('filters middleware', () => {
|
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
it('decodes URI components', () => {
|
|
30
|
+
const request = { query: { filter: 'status=foo%2Cbar!!%2C%3Fbaz%3D!%3Equx' } } as express.Request<
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
any,
|
|
33
|
+
object,
|
|
34
|
+
object,
|
|
35
|
+
{ filter: string }
|
|
36
|
+
>;
|
|
37
|
+
const response = { locals: {} } as express.Response;
|
|
38
|
+
|
|
39
|
+
middleware(request, response, () => {});
|
|
40
|
+
|
|
41
|
+
assert.deepEqual(response.locals, {
|
|
42
|
+
filters: [{ field: 'status', operator: OperatorType.EQUAL, values: ['foo,bar!!,?baz=!>qux'] }],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
29
46
|
it('no data', () => {
|
|
30
47
|
const request = { query: {} } as express.Request;
|
|
31
48
|
const response = { locals: {} } as express.Response;
|
|
@@ -27,11 +27,9 @@ describe('logger middleware', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('additional context', () => {
|
|
30
|
-
const additional =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}),
|
|
34
|
-
).toString('base64');
|
|
30
|
+
const additional = JSON.stringify({
|
|
31
|
+
foo: 'bar',
|
|
32
|
+
});
|
|
35
33
|
|
|
36
34
|
const request = { header: (_key: string) => additional } as express.Request;
|
|
37
35
|
const response = { locals: { correlationId: '123' } } as express.Response;
|
|
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
3
|
|
|
4
4
|
import { Provider } from '../../src/resources/provider.js';
|
|
5
|
+
import Logger from '../../src/resources/logger.js';
|
|
5
6
|
|
|
6
7
|
describe('Provider', () => {
|
|
7
8
|
const provider = new Provider({
|
|
@@ -16,6 +17,8 @@ describe('Provider', () => {
|
|
|
16
17
|
},
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
const logger = new Logger();
|
|
21
|
+
|
|
19
22
|
it('get', async context => {
|
|
20
23
|
const response = new Response('{"data": "value"}', {
|
|
21
24
|
status: 200,
|
|
@@ -26,6 +29,7 @@ describe('Provider', () => {
|
|
|
26
29
|
|
|
27
30
|
const actualResponse = await provider.get('/endpoint', {
|
|
28
31
|
credentials: { apiKey: 'apikey#1111' },
|
|
32
|
+
logger: logger,
|
|
29
33
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
30
34
|
});
|
|
31
35
|
|
|
@@ -62,6 +66,7 @@ describe('Provider', () => {
|
|
|
62
66
|
},
|
|
63
67
|
{
|
|
64
68
|
credentials: { apiKey: 'apikey#1111' },
|
|
69
|
+
logger: logger,
|
|
65
70
|
additionnalheaders: { 'X-Additional-Header': 'value1' },
|
|
66
71
|
},
|
|
67
72
|
);
|
|
@@ -100,6 +105,7 @@ describe('Provider', () => {
|
|
|
100
105
|
},
|
|
101
106
|
{
|
|
102
107
|
credentials: { apiKey: 'apikey#1111' },
|
|
108
|
+
logger: logger,
|
|
103
109
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
104
110
|
},
|
|
105
111
|
);
|
|
@@ -137,6 +143,7 @@ describe('Provider', () => {
|
|
|
137
143
|
},
|
|
138
144
|
{
|
|
139
145
|
credentials: { apiKey: 'apikey#1111' },
|
|
146
|
+
logger: logger,
|
|
140
147
|
queryParams: { param1: 'value1', param2: 'value2' },
|
|
141
148
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
142
149
|
},
|
|
@@ -170,6 +177,7 @@ describe('Provider', () => {
|
|
|
170
177
|
|
|
171
178
|
const actualResponse = await provider.delete('/endpoint/123', {
|
|
172
179
|
credentials: { apiKey: 'apikey#1111' },
|
|
180
|
+
logger: logger,
|
|
173
181
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
174
182
|
});
|
|
175
183
|
|
|
@@ -216,6 +224,7 @@ describe('Provider', () => {
|
|
|
216
224
|
|
|
217
225
|
const options = {
|
|
218
226
|
credentials: { apiKey: 'apikey#1111' },
|
|
227
|
+
logger: logger,
|
|
219
228
|
additionnalheaders: { 'X-Additional-Header': 'value1', 'Content-Type': 'application/json' },
|
|
220
229
|
};
|
|
221
230
|
|
|
@@ -251,6 +260,7 @@ describe('Provider', () => {
|
|
|
251
260
|
assert.rejects(() =>
|
|
252
261
|
provider.get('/endpoint/123', {
|
|
253
262
|
credentials: { apiKey: 'apikey#1111' },
|
|
263
|
+
logger: logger,
|
|
254
264
|
}),
|
|
255
265
|
);
|
|
256
266
|
});
|
|
@@ -265,6 +275,7 @@ describe('Provider', () => {
|
|
|
265
275
|
assert.rejects(() =>
|
|
266
276
|
provider.get('/endpoint/123', {
|
|
267
277
|
credentials: { apiKey: 'apikey#1111' },
|
|
278
|
+
logger: logger,
|
|
268
279
|
}),
|
|
269
280
|
);
|
|
270
281
|
});
|
|
@@ -279,6 +290,7 @@ describe('Provider', () => {
|
|
|
279
290
|
assert.rejects(() =>
|
|
280
291
|
provider.get('/endpoint/123', {
|
|
281
292
|
credentials: { apiKey: 'apikey#1111' },
|
|
293
|
+
logger: logger,
|
|
282
294
|
}),
|
|
283
295
|
);
|
|
284
296
|
});
|