@data-fair/lib-node 2.8.1 → 2.9.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/axios-auth.js +77 -65
- package/axios.js +50 -46
- package/events-queue.d.ts +5 -3
- package/events-queue.js +107 -101
- package/http-agents.js +44 -44
- package/locks.js +72 -70
- package/mongo.js +72 -68
- package/observer.js +86 -79
- package/package.json +1 -1
- package/session.d.ts +10 -0
- package/session.js +110 -0
- package/test-module-hooks.js +12 -12
- package/test-spies.js +39 -38
- package/upgrade-scripts.js +76 -70
- package/validation.js +1 -1
- package/ws-client.js +89 -85
- package/ws-emitter.js +13 -11
package/axios-auth.js
CHANGED
|
@@ -1,69 +1,81 @@
|
|
|
1
1
|
// build axios instances with sessions on a simple-directory instance
|
|
2
2
|
// WARNING: use for integration testing only
|
|
3
|
-
import { Agent } from 'node:http'
|
|
4
|
-
import { axiosBuilder, axiosInstance } from './axios.js'
|
|
5
|
-
import { CookieJar } from 'tough-cookie'
|
|
3
|
+
import { Agent } from 'node:http';
|
|
4
|
+
import { axiosBuilder, axiosInstance } from './axios.js';
|
|
5
|
+
import { CookieJar } from 'tough-cookie';
|
|
6
6
|
const getConfigUrl = (config) => {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if (!config.url)
|
|
8
|
+
throw new Error('url is required');
|
|
9
|
+
return new URL((config.baseURL && config.url?.startsWith('/')) ? (config.baseURL + config.url) : config.url);
|
|
10
|
+
};
|
|
11
|
+
export async function axiosAuth(opts) {
|
|
12
|
+
const body = { email: opts.email, password: opts.password };
|
|
13
|
+
if (opts.org)
|
|
14
|
+
body.org = opts.org;
|
|
15
|
+
if (opts.dep)
|
|
16
|
+
body.dep = opts.dep;
|
|
17
|
+
if (opts.adminMode)
|
|
18
|
+
body.adminMode = opts.adminMode;
|
|
19
|
+
if (opts.orgStorage)
|
|
20
|
+
body.orgStorage = opts.orgStorage;
|
|
21
|
+
const axiosOpts = {
|
|
22
|
+
httpAgent: new Agent({ keepAlive: false }),
|
|
23
|
+
maxRedirects: 0,
|
|
24
|
+
...opts.axiosOpts
|
|
25
|
+
};
|
|
26
|
+
const directoryUrl = opts.directoryUrl ?? 'http://localhost:8080';
|
|
27
|
+
let callbackUrl = (await axiosInstance.post(directoryUrl + '/api/auth/password', body, { params: { redirect: directoryUrl }, maxRedirects: 0 })).data;
|
|
28
|
+
if (callbackUrl.startsWith(directoryUrl + '/simple-directory')) {
|
|
29
|
+
callbackUrl = callbackUrl.replace(directoryUrl + '/simple-directory', directoryUrl);
|
|
30
|
+
}
|
|
31
|
+
const cookieJar = new CookieJar();
|
|
32
|
+
const sdOrigin = new URL(directoryUrl).origin;
|
|
33
|
+
try {
|
|
34
|
+
await axiosInstance.get(callbackUrl, { maxRedirects: 0 });
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err.status !== 302)
|
|
38
|
+
throw err;
|
|
39
|
+
const redirectUrl = new URL(err.headers.location);
|
|
40
|
+
const redirectError = redirectUrl.searchParams.get('error');
|
|
41
|
+
if (redirectError)
|
|
42
|
+
throw new Error(redirectError);
|
|
43
|
+
for (const cookie of err.headers['set-cookie'] ?? [])
|
|
44
|
+
cookieJar.setCookieSync(cookie, sdOrigin);
|
|
45
|
+
}
|
|
46
|
+
const ax = axiosBuilder(axiosOpts, (ax) => {
|
|
47
|
+
ax.interceptors.request.use((config) => {
|
|
48
|
+
const url = getConfigUrl(config);
|
|
49
|
+
// if opts.globalCookies is true the cookie jar is used to store cookies shared accross all domains
|
|
50
|
+
// this behavior is useful for testing purposes if serves are not on the same domain
|
|
51
|
+
if (!opts.globalCookies || url.origin === sdOrigin) {
|
|
52
|
+
config.headers.Cookie = cookieJar.getCookiesSync(url.href);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
config.headers.Cookie = cookieJar.getCookiesSync(sdOrigin);
|
|
56
|
+
}
|
|
57
|
+
return config;
|
|
58
|
+
});
|
|
59
|
+
ax.interceptors.response.use((res) => {
|
|
60
|
+
const origin = opts.globalCookies ? sdOrigin : getConfigUrl(res.config).origin;
|
|
61
|
+
for (const cookie of res.headers['set-cookie'] ?? [])
|
|
62
|
+
cookieJar.setCookieSync(cookie, origin);
|
|
63
|
+
return res;
|
|
64
|
+
}, async (error) => {
|
|
65
|
+
const res = error.response;
|
|
66
|
+
if (res) {
|
|
67
|
+
const origin = opts.globalCookies ? sdOrigin : getConfigUrl(res.config).origin;
|
|
68
|
+
for (const cookie of res.headers['set-cookie'] ?? [])
|
|
69
|
+
cookieJar.setCookieSync(cookie, origin);
|
|
70
|
+
}
|
|
71
|
+
return Promise.reject(error);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
ax.cookieJar = cookieJar;
|
|
75
|
+
ax.setOrg = (org, dep) => {
|
|
76
|
+
cookieJar.setCookieSync(`id_token_org=${org}`, sdOrigin);
|
|
77
|
+
cookieJar.setCookieSync(`id_token_dep=${dep ?? ''}`, sdOrigin);
|
|
78
|
+
};
|
|
79
|
+
return ax;
|
|
9
80
|
}
|
|
10
|
-
export
|
|
11
|
-
const body = { email: opts.email, password: opts.password }
|
|
12
|
-
if (opts.org) { body.org = opts.org }
|
|
13
|
-
if (opts.dep) { body.dep = opts.dep }
|
|
14
|
-
if (opts.adminMode) { body.adminMode = opts.adminMode }
|
|
15
|
-
if (opts.orgStorage) { body.orgStorage = opts.orgStorage }
|
|
16
|
-
const axiosOpts = {
|
|
17
|
-
httpAgent: new Agent({ keepAlive: false }),
|
|
18
|
-
maxRedirects: 0,
|
|
19
|
-
...opts.axiosOpts
|
|
20
|
-
}
|
|
21
|
-
const directoryUrl = opts.directoryUrl ?? 'http://localhost:8080'
|
|
22
|
-
let callbackUrl = (await axiosInstance.post(directoryUrl + '/api/auth/password', body, { params: { redirect: directoryUrl }, maxRedirects: 0 })).data
|
|
23
|
-
if (callbackUrl.startsWith(directoryUrl + '/simple-directory')) {
|
|
24
|
-
callbackUrl = callbackUrl.replace(directoryUrl + '/simple-directory', directoryUrl)
|
|
25
|
-
}
|
|
26
|
-
const cookieJar = new CookieJar()
|
|
27
|
-
const sdOrigin = new URL(directoryUrl).origin
|
|
28
|
-
try {
|
|
29
|
-
await axiosInstance.get(callbackUrl, { maxRedirects: 0 })
|
|
30
|
-
} catch (err) {
|
|
31
|
-
if (err.status !== 302) { throw err }
|
|
32
|
-
const redirectUrl = new URL(err.headers.location)
|
|
33
|
-
const redirectError = redirectUrl.searchParams.get('error')
|
|
34
|
-
if (redirectError) { throw new Error(redirectError) }
|
|
35
|
-
for (const cookie of err.headers['set-cookie'] ?? []) { cookieJar.setCookieSync(cookie, sdOrigin) }
|
|
36
|
-
}
|
|
37
|
-
const ax = axiosBuilder(axiosOpts, (ax) => {
|
|
38
|
-
ax.interceptors.request.use((config) => {
|
|
39
|
-
const url = getConfigUrl(config)
|
|
40
|
-
// if opts.globalCookies is true the cookie jar is used to store cookies shared accross all domains
|
|
41
|
-
// this behavior is useful for testing purposes if serves are not on the same domain
|
|
42
|
-
if (!opts.globalCookies || url.origin === sdOrigin) {
|
|
43
|
-
config.headers.Cookie = cookieJar.getCookiesSync(url.href)
|
|
44
|
-
} else {
|
|
45
|
-
config.headers.Cookie = cookieJar.getCookiesSync(sdOrigin)
|
|
46
|
-
}
|
|
47
|
-
return config
|
|
48
|
-
})
|
|
49
|
-
ax.interceptors.response.use((res) => {
|
|
50
|
-
const origin = opts.globalCookies ? sdOrigin : getConfigUrl(res.config).origin
|
|
51
|
-
for (const cookie of res.headers['set-cookie'] ?? []) { cookieJar.setCookieSync(cookie, origin) }
|
|
52
|
-
return res
|
|
53
|
-
}, async (error) => {
|
|
54
|
-
const res = error.response
|
|
55
|
-
if (res) {
|
|
56
|
-
const origin = opts.globalCookies ? sdOrigin : getConfigUrl(res.config).origin
|
|
57
|
-
for (const cookie of res.headers['set-cookie'] ?? []) { cookieJar.setCookieSync(cookie, origin) }
|
|
58
|
-
}
|
|
59
|
-
return Promise.reject(error)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
ax.cookieJar = cookieJar
|
|
63
|
-
ax.setOrg = (org, dep) => {
|
|
64
|
-
cookieJar.setCookieSync(`id_token_org=${org}`, sdOrigin)
|
|
65
|
-
cookieJar.setCookieSync(`id_token_dep=${dep ?? ''}`, sdOrigin)
|
|
66
|
-
}
|
|
67
|
-
return ax
|
|
68
|
-
}
|
|
69
|
-
export default axiosAuth
|
|
81
|
+
export default axiosAuth;
|
package/axios.js
CHANGED
|
@@ -1,51 +1,55 @@
|
|
|
1
1
|
// prepare an axios instance with improved error management and some performance tuning for nodejs usage
|
|
2
|
-
import axios from 'axios'
|
|
3
|
-
import { httpAgent, httpsAgent } from './http-agents.js'
|
|
4
|
-
const { stackTraceLimit } = Error
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { httpAgent, httpsAgent } from './http-agents.js';
|
|
4
|
+
const { stackTraceLimit } = Error;
|
|
5
5
|
class AxiosRequestError extends Error {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.name = 'AxiosRequestError';
|
|
9
|
+
}
|
|
10
10
|
}
|
|
11
11
|
const requestInterceptor = (config) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
export function axiosBuilder
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
12
|
+
// better stack traces
|
|
13
|
+
// see https://github.com/axios/axios/issues/2387#issuecomment-652242713
|
|
14
|
+
Error.stackTraceLimit = 0;
|
|
15
|
+
const errorContext = new AxiosRequestError();
|
|
16
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
17
|
+
Error.captureStackTrace(errorContext, requestInterceptor);
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
config.errorContext = errorContext;
|
|
20
|
+
return config;
|
|
21
|
+
};
|
|
22
|
+
export function axiosBuilder(opts = {}, beforeInterceptors) {
|
|
23
|
+
const ax = axios.create({
|
|
24
|
+
httpAgent,
|
|
25
|
+
httpsAgent,
|
|
26
|
+
...opts
|
|
27
|
+
});
|
|
28
|
+
if (beforeInterceptors)
|
|
29
|
+
beforeInterceptors(ax);
|
|
30
|
+
ax.interceptors.request.use(requestInterceptor);
|
|
31
|
+
// shorter stack traces
|
|
32
|
+
ax.interceptors.response.use(response => response, error => {
|
|
33
|
+
if (!error.response) {
|
|
34
|
+
if (error.config?.errorContext?.stack)
|
|
35
|
+
error.stack += '\nRequest context:\n' + error.config?.errorContext?.stack;
|
|
36
|
+
return Promise.reject(error);
|
|
37
|
+
}
|
|
38
|
+
delete error.response.request;
|
|
39
|
+
error.response.config = { method: error.response.config.method, url: error.response.config.url, data: error.response.config.data };
|
|
40
|
+
if (error.response.config.data && error.response.config.data._writableState)
|
|
41
|
+
delete error.response.config.data;
|
|
42
|
+
if (error.response.data && error.response.data._readableState)
|
|
43
|
+
delete error.response.data;
|
|
44
|
+
let messageText = error.response.statusText;
|
|
45
|
+
if (error.response.data) {
|
|
46
|
+
messageText = typeof error.response.data === 'string' ? error.response.data : JSON.stringify(error.response.data);
|
|
47
|
+
}
|
|
48
|
+
error.response.message = `${error.response.status} - ${messageText}`;
|
|
49
|
+
Object.assign(error.config.errorContext, error.response);
|
|
50
|
+
return Promise.reject(error.config.errorContext);
|
|
51
|
+
});
|
|
52
|
+
return ax;
|
|
49
53
|
}
|
|
50
|
-
export const axiosInstance = axiosBuilder()
|
|
51
|
-
export default axiosInstance
|
|
54
|
+
export const axiosInstance = axiosBuilder();
|
|
55
|
+
export default axiosInstance;
|
package/events-queue.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type Event } from '@data-fair/lib-common-types/event/index.js';
|
|
2
2
|
import { type Notification } from '@data-fair/lib-common-types/notification/index.js';
|
|
3
3
|
import { SessionState } from '@data-fair/lib-common-types/session/index.js';
|
|
4
|
-
type EventsQueueOptions = {
|
|
4
|
+
export type EventsQueueOptions = {
|
|
5
5
|
eventsUrl: string;
|
|
6
6
|
eventsSecret: string;
|
|
7
7
|
inactive?: boolean;
|
|
8
8
|
};
|
|
9
|
+
export type PushEvent = Omit<Event, 'date'>;
|
|
10
|
+
export type PushNotification = Omit<Notification, 'date'>;
|
|
9
11
|
export declare class EventsQueue {
|
|
10
12
|
_options: EventsQueueOptions | undefined;
|
|
11
13
|
eventsQueue: Event[];
|
|
@@ -17,8 +19,8 @@ export declare class EventsQueue {
|
|
|
17
19
|
stop: () => Promise<void>;
|
|
18
20
|
loop(): Promise<void>;
|
|
19
21
|
drain(): Promise<void>;
|
|
20
|
-
pushEvent(event:
|
|
21
|
-
pushNotification(notification:
|
|
22
|
+
pushEvent(event: PushEvent, sessionState?: SessionState): void;
|
|
23
|
+
pushNotification(notification: PushNotification): void;
|
|
22
24
|
}
|
|
23
25
|
declare const eventsQueue: EventsQueue;
|
|
24
26
|
export default eventsQueue;
|
package/events-queue.js
CHANGED
|
@@ -1,110 +1,116 @@
|
|
|
1
|
-
import { internalError } from '@data-fair/lib-node/observer.js'
|
|
2
|
-
import axios from './axios.js'
|
|
3
|
-
import Debug from 'debug'
|
|
4
|
-
const debug = Debug('events-queue')
|
|
1
|
+
import { internalError } from '@data-fair/lib-node/observer.js';
|
|
2
|
+
import axios from './axios.js';
|
|
3
|
+
import Debug from 'debug';
|
|
4
|
+
const debug = Debug('events-queue');
|
|
5
5
|
export class EventsQueue {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
start = async (options) => {
|
|
17
|
-
if (this._options) { throw new Error('events queue was already initialized') }
|
|
18
|
-
this._options = options
|
|
19
|
-
this.loop()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
stop = async () => {
|
|
23
|
-
this.stopped = true
|
|
24
|
-
if (this.currentDrainPromise) { await this.currentDrainPromise }
|
|
25
|
-
await this.drain()
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async loop () {
|
|
29
|
-
while (!this.stopped) {
|
|
30
|
-
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
31
|
-
this.currentDrainPromise = this.drain()
|
|
32
|
-
await this.currentDrainPromise
|
|
33
|
-
this.currentDrainPromise = undefined
|
|
6
|
+
_options;
|
|
7
|
+
eventsQueue = [];
|
|
8
|
+
notificationsQueue = [];
|
|
9
|
+
stopped = false;
|
|
10
|
+
currentDrainPromise;
|
|
11
|
+
options() {
|
|
12
|
+
if (!this._options)
|
|
13
|
+
throw new Error('events queue was not initialized');
|
|
14
|
+
return this._options;
|
|
34
15
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
debug('drain notifications', notifications.length)
|
|
54
|
-
for (const notification of notifications) {
|
|
55
|
-
try {
|
|
56
|
-
await axios.post(this.options().eventsUrl + '/api/notifications', notification, { headers: { 'x-secret-key': this.options().eventsSecret } })
|
|
57
|
-
} catch (err) {
|
|
58
|
-
internalError('events-queue-push-notif', err)
|
|
59
|
-
// retry later
|
|
60
|
-
if (err.status >= 500 && (notifications.length + this.notificationsQueue.length) < 1000) {
|
|
61
|
-
this.notificationsQueue.unshift()
|
|
62
|
-
}
|
|
16
|
+
start = async (options) => {
|
|
17
|
+
if (this._options)
|
|
18
|
+
throw new Error('events queue was already initialized');
|
|
19
|
+
this._options = options;
|
|
20
|
+
this.loop();
|
|
21
|
+
};
|
|
22
|
+
stop = async () => {
|
|
23
|
+
this.stopped = true;
|
|
24
|
+
if (this.currentDrainPromise)
|
|
25
|
+
await this.currentDrainPromise;
|
|
26
|
+
await this.drain();
|
|
27
|
+
};
|
|
28
|
+
async loop() {
|
|
29
|
+
while (!this.stopped) {
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
31
|
+
this.currentDrainPromise = this.drain();
|
|
32
|
+
await this.currentDrainPromise;
|
|
33
|
+
this.currentDrainPromise = undefined;
|
|
63
34
|
}
|
|
64
|
-
}
|
|
65
35
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
event.originator = {
|
|
81
|
-
user: { id: sessionState.user.id, name: sessionState.user.name, admin: true }
|
|
36
|
+
async drain() {
|
|
37
|
+
if (this.eventsQueue.length) {
|
|
38
|
+
const events = this.eventsQueue;
|
|
39
|
+
this.eventsQueue = [];
|
|
40
|
+
debug('drain events', events.length);
|
|
41
|
+
try {
|
|
42
|
+
await axios.post(this.options().eventsUrl + '/api/events', events, { headers: { 'x-secret-key': this.options().eventsSecret } });
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
internalError('events-queue-push-event', err);
|
|
46
|
+
// retry later
|
|
47
|
+
if (err.status >= 500 && events.length < 1000)
|
|
48
|
+
this.eventsQueue = events.concat(this.eventsQueue);
|
|
49
|
+
}
|
|
82
50
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
51
|
+
if (this.notificationsQueue.length) {
|
|
52
|
+
const notifications = this.notificationsQueue;
|
|
53
|
+
this.notificationsQueue = [];
|
|
54
|
+
debug('drain notifications', notifications.length);
|
|
55
|
+
for (const notification of notifications) {
|
|
56
|
+
try {
|
|
57
|
+
await axios.post(this.options().eventsUrl + '/api/notifications', notification, { headers: { 'x-secret-key': this.options().eventsSecret } });
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
internalError('events-queue-push-notif', err);
|
|
61
|
+
// retry later
|
|
62
|
+
if (err.status >= 500 && (notifications.length + this.notificationsQueue.length) < 1000) {
|
|
63
|
+
this.notificationsQueue.unshift();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
86
67
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
68
|
+
}
|
|
69
|
+
pushEvent(event, sessionState) {
|
|
70
|
+
const options = this.options();
|
|
71
|
+
if (options.inactive)
|
|
72
|
+
return;
|
|
73
|
+
if (this.stopped)
|
|
74
|
+
throw new Error('events queue has been stopped');
|
|
75
|
+
event.date = new Date().toISOString();
|
|
76
|
+
debug('pushEvent', event);
|
|
77
|
+
if (!event.originator && sessionState?.user && sessionState.account) {
|
|
78
|
+
if (sessionState.user.asAdmin) {
|
|
79
|
+
event.originator = {
|
|
80
|
+
user: { ...sessionState.user.asAdmin, admin: true }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
else if (sessionState.user.adminMode && (sessionState.account.type !== event.sender?.type || sessionState.account.id !== event.sender?.id)) {
|
|
84
|
+
event.originator = {
|
|
85
|
+
user: { id: sessionState.user.id, name: sessionState.user.name, admin: true }
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
event.originator = {
|
|
90
|
+
user: { id: sessionState.user.id, name: sessionState.user.name }
|
|
91
|
+
};
|
|
92
|
+
if (sessionState.organization) {
|
|
93
|
+
event.originator.organization = {
|
|
94
|
+
id: sessionState.organization.id,
|
|
95
|
+
name: sessionState.organization.name,
|
|
96
|
+
department: sessionState.organization.department,
|
|
97
|
+
departmentName: sessionState.organization.departmentName
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
94
101
|
}
|
|
95
|
-
|
|
102
|
+
this.eventsQueue.push(event);
|
|
103
|
+
}
|
|
104
|
+
pushNotification(notification) {
|
|
105
|
+
const options = this.options();
|
|
106
|
+
if (options.inactive)
|
|
107
|
+
return;
|
|
108
|
+
if (this.stopped)
|
|
109
|
+
throw new Error('notifications queue has been stopped');
|
|
110
|
+
notification.date = new Date().toISOString();
|
|
111
|
+
debug('pushNotification', notification);
|
|
112
|
+
this.notificationsQueue.push(notification);
|
|
96
113
|
}
|
|
97
|
-
this.eventsQueue.push(event)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
pushNotification (notification) {
|
|
101
|
-
const options = this.options()
|
|
102
|
-
if (options.inactive) { return }
|
|
103
|
-
if (this.stopped) { throw new Error('notifications queue has been stopped') }
|
|
104
|
-
notification.date = new Date().toISOString()
|
|
105
|
-
debug('pushNotification', notification)
|
|
106
|
-
this.notificationsQueue.push(notification)
|
|
107
|
-
}
|
|
108
114
|
}
|
|
109
|
-
const eventsQueue = new EventsQueue()
|
|
110
|
-
export default eventsQueue
|
|
115
|
+
const eventsQueue = new EventsQueue();
|
|
116
|
+
export default eventsQueue;
|
package/http-agents.js
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
// use better DNS lookup than nodejs default and try to reduce number of socket openings
|
|
2
|
-
import CacheableLookup from 'cacheable-lookup'
|
|
3
|
-
import HttpAgent, { HttpsAgent } from 'agentkeepalive'
|
|
4
|
-
import { Counter, Gauge } from 'prom-client'
|
|
2
|
+
import CacheableLookup from 'cacheable-lookup';
|
|
3
|
+
import HttpAgent, { HttpsAgent } from 'agentkeepalive';
|
|
4
|
+
import { Counter, Gauge } from 'prom-client';
|
|
5
5
|
// not very high number of sockets but we don't want to saturate our reverse proxies
|
|
6
6
|
// if a higher number is needed the service should probably be scaled anyway
|
|
7
7
|
const keepaliveOpts = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
export const httpAgent = new HttpAgent(keepaliveOpts)
|
|
14
|
-
export const httpsAgent = new HttpsAgent(keepaliveOpts)
|
|
15
|
-
const cacheableLookup = new CacheableLookup()
|
|
16
|
-
cacheableLookup.install(httpAgent)
|
|
17
|
-
cacheableLookup.install(httpsAgent)
|
|
8
|
+
maxSockets: 8,
|
|
9
|
+
maxFreeSockets: 8,
|
|
10
|
+
timeout: 60000,
|
|
11
|
+
freeSocketTimeout: 30000
|
|
12
|
+
};
|
|
13
|
+
export const httpAgent = new HttpAgent(keepaliveOpts);
|
|
14
|
+
export const httpsAgent = new HttpsAgent(keepaliveOpts);
|
|
15
|
+
const cacheableLookup = new CacheableLookup();
|
|
16
|
+
cacheableLookup.install(httpAgent);
|
|
17
|
+
cacheableLookup.install(httpsAgent);
|
|
18
18
|
// monitor agent statuses
|
|
19
|
-
const agents = { http: httpAgent, https: httpsAgent }
|
|
19
|
+
const agents = { http: httpAgent, https: httpsAgent };
|
|
20
20
|
const socketsGauge = new Gauge({
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
21
|
+
name: 'df_http_agent_sockets',
|
|
22
|
+
help: 'Number of open sockets in the http agent',
|
|
23
|
+
labelNames: ['protocol', 'host']
|
|
24
|
+
});
|
|
25
25
|
const freeSocketsGauge = new Gauge({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
})
|
|
26
|
+
name: 'df_http_agent_free_sockets',
|
|
27
|
+
help: 'Number of free sockets in the http agent',
|
|
28
|
+
labelNames: ['protocol', 'host']
|
|
29
|
+
});
|
|
30
30
|
const createSocketCounter = new Counter({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
31
|
+
name: 'df_http_agent_create_socket_total',
|
|
32
|
+
help: 'Total number of sockets created by the http agent',
|
|
33
|
+
labelNames: ['protocol']
|
|
34
|
+
});
|
|
35
35
|
const requestCounter = new Counter({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
})
|
|
36
|
+
name: 'df_http_agent_request_total',
|
|
37
|
+
help: 'Total number of requests created by the http agent',
|
|
38
|
+
labelNames: ['protocol']
|
|
39
|
+
});
|
|
40
40
|
setInterval(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
for (const _protocol of ['http', 'https']) {
|
|
42
|
+
const protocol = _protocol;
|
|
43
|
+
const agentStatus = agents[protocol].getCurrentStatus();
|
|
44
|
+
for (const host in agentStatus.sockets) {
|
|
45
|
+
socketsGauge.set({ protocol, host }, agentStatus.sockets[host]);
|
|
46
|
+
}
|
|
47
|
+
for (const host in agentStatus.freeSockets) {
|
|
48
|
+
freeSocketsGauge.set({ protocol, host }, agentStatus.freeSockets[host]);
|
|
49
|
+
}
|
|
50
|
+
createSocketCounter.remove({ protocol });
|
|
51
|
+
createSocketCounter.inc({ protocol }, agentStatus.createSocketCount);
|
|
52
|
+
requestCounter.remove({ protocol });
|
|
53
|
+
requestCounter.inc({ protocol }, agentStatus.requestCount);
|
|
46
54
|
}
|
|
47
|
-
|
|
48
|
-
freeSocketsGauge.set({ protocol, host }, agentStatus.freeSockets[host])
|
|
49
|
-
}
|
|
50
|
-
createSocketCounter.remove({ protocol })
|
|
51
|
-
createSocketCounter.inc({ protocol }, agentStatus.createSocketCount)
|
|
52
|
-
requestCounter.remove({ protocol })
|
|
53
|
-
requestCounter.inc({ protocol }, agentStatus.requestCount)
|
|
54
|
-
}
|
|
55
|
-
}, 60000)
|
|
55
|
+
}, 60000);
|