akeyless-server-commons 1.0.203 → 1.0.204

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.
Files changed (99) hide show
  1. package/dist/cjs/helpers/index.d.ts +0 -1
  2. package/dist/cjs/helpers/index.js +0 -1
  3. package/dist/cjs/helpers/index.js.map +1 -1
  4. package/dist/cjs/helpers/start.js +16 -18
  5. package/dist/cjs/helpers/start.js.map +1 -1
  6. package/dist/cjs/managers/index.d.ts +1 -0
  7. package/dist/cjs/managers/index.js +1 -0
  8. package/dist/cjs/managers/index.js.map +1 -1
  9. package/dist/cjs/managers/rabbitmq_manager.d.ts +60 -0
  10. package/dist/cjs/managers/rabbitmq_manager.js +190 -0
  11. package/dist/cjs/managers/rabbitmq_manager.js.map +1 -0
  12. package/dist/esm/helpers/index.d.ts +0 -1
  13. package/dist/esm/helpers/index.js +0 -1
  14. package/dist/esm/helpers/index.js.map +1 -1
  15. package/dist/esm/helpers/start.js +16 -18
  16. package/dist/esm/helpers/start.js.map +1 -1
  17. package/dist/esm/managers/index.d.ts +1 -0
  18. package/dist/esm/managers/index.js +1 -0
  19. package/dist/esm/managers/index.js.map +1 -1
  20. package/dist/esm/managers/rabbitmq_manager.d.ts +60 -0
  21. package/dist/esm/managers/rabbitmq_manager.js +183 -0
  22. package/dist/esm/managers/rabbitmq_manager.js.map +1 -0
  23. package/dist/helpers/boards_helpers.js +62 -0
  24. package/dist/helpers/email_helpers.js +153 -0
  25. package/dist/helpers/firebase_helpers.js +601 -0
  26. package/dist/helpers/global_helpers.js +147 -0
  27. package/dist/helpers/index.js +12 -0
  28. package/dist/helpers/location_helpers.js +15 -0
  29. package/dist/helpers/login_helpers.js +18 -0
  30. package/dist/helpers/notification_helpers.js +248 -0
  31. package/dist/helpers/phone_number_helpers.js +96 -0
  32. package/dist/helpers/redis/helpers.js +215 -0
  33. package/dist/helpers/redis/index.js +4 -0
  34. package/dist/helpers/redis/initialize.js +139 -0
  35. package/dist/helpers/redis/keys.js +23 -0
  36. package/dist/helpers/redis/snapshot.js +166 -0
  37. package/dist/helpers/start.js +83 -0
  38. package/dist/helpers/tasks_helpers.js +139 -0
  39. package/dist/helpers/time_helpers.js +167 -0
  40. package/dist/index.js +5 -0
  41. package/dist/managers/cache_manager.js +37 -0
  42. package/dist/managers/index.js +4 -0
  43. package/dist/managers/logger_manager.js +68 -0
  44. package/dist/managers/rabbitmq_manager.js +179 -0
  45. package/dist/managers/translation_manager.js +38 -0
  46. package/dist/middlewares/auth_mw.js +50 -0
  47. package/dist/middlewares/error_handling.js +20 -0
  48. package/dist/middlewares/global_mw.js +119 -0
  49. package/dist/middlewares/index.js +4 -0
  50. package/dist/middlewares/trim_mw.js +7 -0
  51. package/dist/test.js +1 -0
  52. package/dist/types/enums/global.js +8 -0
  53. package/dist/types/enums/index.js +1 -0
  54. package/dist/types/helpers/boards_helpers.d.ts +10 -0
  55. package/dist/types/helpers/email_helpers.d.ts +12 -0
  56. package/dist/types/helpers/firebase_helpers.d.ts +56 -0
  57. package/dist/types/helpers/global_helpers.d.ts +16 -0
  58. package/dist/types/helpers/index.d.ts +12 -0
  59. package/dist/types/helpers/location_helpers.d.ts +3 -0
  60. package/dist/types/helpers/login_helpers.d.ts +4 -0
  61. package/dist/types/helpers/notification_helpers.d.ts +11 -0
  62. package/dist/types/helpers/phone_number_helpers.d.ts +19 -0
  63. package/dist/types/helpers/redis/helpers.d.ts +11 -0
  64. package/dist/types/helpers/redis/index.d.ts +4 -0
  65. package/dist/types/helpers/redis/initialize.d.ts +6 -0
  66. package/dist/types/helpers/redis/keys.d.ts +5 -0
  67. package/dist/types/helpers/redis/snapshot.d.ts +2 -0
  68. package/dist/types/helpers/start.d.ts +4 -0
  69. package/dist/types/helpers/tasks_helpers.d.ts +25 -0
  70. package/dist/types/helpers/time_helpers.d.ts +40 -0
  71. package/dist/types/index.d.ts +5 -0
  72. package/dist/types/index.js +3 -0
  73. package/dist/types/interfaces/email.js +1 -0
  74. package/dist/types/interfaces/global.js +1 -0
  75. package/dist/types/interfaces/index.js +2 -0
  76. package/dist/types/managers/cache_manager.d.ts +12 -0
  77. package/dist/types/managers/index.d.ts +4 -0
  78. package/dist/types/managers/logger_manager.d.ts +14 -0
  79. package/dist/types/managers/rabbitmq_manager.d.ts +60 -0
  80. package/dist/types/managers/translation_manager.d.ts +14 -0
  81. package/dist/types/middlewares/auth_mw.d.ts +4 -0
  82. package/dist/types/middlewares/error_handling.d.ts +11 -0
  83. package/dist/types/middlewares/global_mw.d.ts +5 -0
  84. package/dist/types/middlewares/index.d.ts +4 -0
  85. package/dist/types/middlewares/trim_mw.d.ts +2 -0
  86. package/dist/types/test.d.ts +0 -0
  87. package/dist/types/types/enums/global.d.ts +9 -0
  88. package/dist/types/types/enums/index.d.ts +1 -0
  89. package/dist/types/types/firebase_types.js +1 -0
  90. package/dist/types/types/global.js +1 -0
  91. package/dist/types/types/index.d.ts +3 -0
  92. package/dist/types/types/index.js +2 -0
  93. package/dist/types/types/interfaces/email.d.ts +33 -0
  94. package/dist/types/types/interfaces/global.d.ts +27 -0
  95. package/dist/types/types/interfaces/index.d.ts +2 -0
  96. package/dist/types/types/types/firebase_types.d.ts +50 -0
  97. package/dist/types/types/types/global.d.ts +17 -0
  98. package/dist/types/types/types/index.d.ts +2 -0
  99. package/package.json +1 -1
@@ -0,0 +1,139 @@
1
+ import { cache_manager, logger } from "../managers";
2
+ import { get_document_by_id_optional, set_document } from "./firebase_helpers";
3
+ import admin from "firebase-admin";
4
+ export var TaskName;
5
+ (function (TaskName) {
6
+ TaskName["send_reset_sms"] = "send_reset_sms";
7
+ TaskName["collect_devices_health"] = "collect_devices_health";
8
+ TaskName["collect_charge_locations"] = "collect_charge_locations";
9
+ TaskName["collect_charge_cdrs"] = "collect_charge_cdrs";
10
+ TaskName["auto_connect_to_call_center"] = "auto_connect_to_call_center";
11
+ TaskName["make_erm_ev_events"] = "make_erm_ev_events";
12
+ TaskName["collect_jimi_cars_odometer"] = "collect_jimi_cars_odometer";
13
+ })(TaskName || (TaskName = {}));
14
+ export var TaskStatus;
15
+ (function (TaskStatus) {
16
+ TaskStatus["running"] = "running";
17
+ TaskStatus["completed"] = "completed";
18
+ TaskStatus["failed"] = "failed";
19
+ TaskStatus["suspeneded"] = "suspeneded";
20
+ })(TaskStatus || (TaskStatus = {}));
21
+ const save_task_data_in_cache = (task_name, data) => {
22
+ if (Array.isArray(data)) {
23
+ cache_manager.setArrayData(task_name, data);
24
+ }
25
+ else {
26
+ cache_manager.setObjectData(task_name, data);
27
+ }
28
+ };
29
+ export const execute_task = async (source, task_name, task, options) => {
30
+ const { debug_logs = true, save_in } = options || {};
31
+ try {
32
+ await set_document("nx-tasks", task_name, {
33
+ source,
34
+ status: TaskStatus.running,
35
+ started: new Date(),
36
+ timestamp: new Date(),
37
+ error: "",
38
+ }, false);
39
+ if (debug_logs) {
40
+ logger.log(`Task [${task_name}] started`);
41
+ }
42
+ const start = performance.now();
43
+ const data = await task();
44
+ const update = {
45
+ status: TaskStatus.completed,
46
+ completed: new Date(),
47
+ timestamp: new Date(),
48
+ };
49
+ if (data) {
50
+ if (save_in === "none") {
51
+ update.data = "no data to save";
52
+ }
53
+ else if (save_in === "storage" && typeof data === "object") {
54
+ save_task_data_in_cache(task_name, data);
55
+ const url = await keep_task_data_in_storage(task_name, data);
56
+ update.data = url;
57
+ }
58
+ else {
59
+ save_task_data_in_cache(task_name, data);
60
+ update.data = data;
61
+ }
62
+ }
63
+ else {
64
+ update.data = "no data to save";
65
+ }
66
+ await set_document("nx-tasks", task_name, update);
67
+ if (debug_logs) {
68
+ logger.log(`Task [${task_name}] ended. It took ${Math.round(performance.now() - start)} ms`);
69
+ }
70
+ }
71
+ catch (exception) {
72
+ const error_for_db = exception instanceof Error ? exception.message : exception;
73
+ logger.error(`Task [${task_name}] error`, error_for_db);
74
+ await set_document("nx-tasks", task_name, {
75
+ status: TaskStatus.failed,
76
+ completed: new Date(),
77
+ timestamp: new Date(),
78
+ error: error_for_db,
79
+ });
80
+ }
81
+ };
82
+ export const get_task_data = async (task_name) => {
83
+ const cached_data = cache_manager.getArrayData(task_name);
84
+ const cached_data_object = cache_manager.getObjectData(task_name, null);
85
+ if (cached_data.length > 0) {
86
+ return cached_data;
87
+ }
88
+ if (cached_data_object) {
89
+ return cached_data;
90
+ }
91
+ const task_data = await get_document_by_id_optional("nx-tasks", task_name);
92
+ if (typeof task_data?.data === "string" && task_data.data.startsWith("http")) {
93
+ const storage_data = await get_task_data_from_storage(task_name);
94
+ if (storage_data) {
95
+ const value = storage_data;
96
+ save_task_data_in_cache(task_name, storage_data);
97
+ return value;
98
+ }
99
+ }
100
+ const value = typeof task_data?.data === "object" ? task_data.data : null;
101
+ if (value) {
102
+ save_task_data_in_cache(task_name, value);
103
+ }
104
+ return value || [];
105
+ };
106
+ export const get_task_data_from_storage = async (task_name) => {
107
+ const bucket = admin.storage().bucket();
108
+ const file = bucket.file(`tasks_data/${task_name}.json`);
109
+ try {
110
+ const [contents] = await file.download();
111
+ return JSON.parse(contents.toString("utf-8"));
112
+ }
113
+ catch (error) {
114
+ logger.error("Task error reading file from Firebase Storage:", error);
115
+ return null;
116
+ }
117
+ };
118
+ export const keep_task_data_in_storage = async (task_name, data) => {
119
+ const bucket = admin.storage().bucket();
120
+ const file = bucket.file(`tasks_data/${task_name}.json`);
121
+ try {
122
+ if (typeof data !== "object") {
123
+ throw new Error("Only arrays or objects can be written");
124
+ }
125
+ const json_string = JSON.stringify(data, null);
126
+ await file.save(json_string, {
127
+ contentType: "application/json",
128
+ });
129
+ const [signed_url] = await file.getSignedUrl({
130
+ action: "read",
131
+ expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
132
+ });
133
+ return signed_url;
134
+ }
135
+ catch (error) {
136
+ logger.error("Error writing file to Firebase Storage:", error);
137
+ return "";
138
+ }
139
+ };
@@ -0,0 +1,167 @@
1
+ import { Timestamp } from "firebase-admin/firestore";
2
+ import moment from "moment";
3
+ export function sort_by_timestamp(a, b, reverse = false) {
4
+ return reverse ? any_datetime_to_millis(b) - any_datetime_to_millis(a) : any_datetime_to_millis(a) - any_datetime_to_millis(b);
5
+ }
6
+ export const calculate_time_passed = (datetime) => {
7
+ const now = new Date();
8
+ const diff_in_milliseconds = Math.max(0, now.getTime() - datetime.getTime());
9
+ const seconds_passed = Math.floor(diff_in_milliseconds / 1000);
10
+ const minutes_passed = Math.floor(seconds_passed / 60);
11
+ const hours_passed = Math.floor(minutes_passed / 60);
12
+ const days_passed = Math.floor(hours_passed / 24);
13
+ const remaining_hours = hours_passed % 24;
14
+ const remaining_minutes = minutes_passed % 60;
15
+ const remaining_seconds = seconds_passed % 60;
16
+ const pad_zero = (value) => (value < 10 ? `0${value}` : `${value}`);
17
+ let parts = [];
18
+ if (days_passed > 0)
19
+ parts.push(days_passed);
20
+ if (remaining_hours > 0 || days_passed > 0)
21
+ parts.push(pad_zero(remaining_hours));
22
+ parts.push(pad_zero(remaining_minutes));
23
+ parts.push(pad_zero(remaining_seconds));
24
+ const time_passed_formatted_short = parts.join(":");
25
+ parts = [];
26
+ if (days_passed > 0)
27
+ parts.push(`${days_passed} d`);
28
+ if (days_passed > 0 || remaining_hours > 0)
29
+ parts.push(`${remaining_hours} h`);
30
+ if (days_passed > 0 || remaining_hours > 0 || remaining_minutes > 0)
31
+ parts.push(`${remaining_minutes} min`);
32
+ parts.push(`${remaining_seconds} sec`);
33
+ const time_passed_formatted_long = parts.join(" ");
34
+ return {
35
+ seconds_passed,
36
+ minutes_passed,
37
+ hours_passed,
38
+ days_passed,
39
+ time_passed_formatted_short,
40
+ time_passed_formatted_long,
41
+ };
42
+ };
43
+ export function any_datetime_to_string(firebaseTimestamp, options) {
44
+ const { defaultReturnedValue, format, fromFormat, tz, debug } = {
45
+ defaultReturnedValue: options?.defaultReturnedValue ?? "-",
46
+ format: options?.format ?? "DD/MM/YYYY HH:mm:ss",
47
+ fromFormat: options?.fromFormat ?? "DD/MM/YYYY HH:mm:ss",
48
+ tz: options?.tz,
49
+ debug: options?.debug ?? false,
50
+ };
51
+ let date;
52
+ switch (true) {
53
+ case !firebaseTimestamp: {
54
+ return defaultReturnedValue;
55
+ }
56
+ case firebaseTimestamp instanceof Timestamp: {
57
+ date = firebaseTimestamp.toDate();
58
+ break;
59
+ }
60
+ case firebaseTimestamp instanceof Date: {
61
+ date = firebaseTimestamp;
62
+ break;
63
+ }
64
+ case typeof firebaseTimestamp === "string" || typeof firebaseTimestamp === "number": {
65
+ const m = moment.utc(firebaseTimestamp, fromFormat);
66
+ switch (m.isValid()) {
67
+ case true:
68
+ date = m.toDate();
69
+ break;
70
+ default:
71
+ return defaultReturnedValue;
72
+ }
73
+ break;
74
+ }
75
+ case !!firebaseTimestamp._seconds: {
76
+ const ft = firebaseTimestamp;
77
+ date = new Date(ft._seconds * 1000 + (ft._nanoseconds ?? 0) / 1000000);
78
+ break;
79
+ }
80
+ case !!firebaseTimestamp.seconds: {
81
+ const ft = firebaseTimestamp;
82
+ date = new Date(ft.seconds * 1000 + ft.nanoseconds / 1000000);
83
+ break;
84
+ }
85
+ default: {
86
+ if (debug) {
87
+ console.error("Invalid timestamp format: ", firebaseTimestamp);
88
+ }
89
+ return defaultReturnedValue;
90
+ }
91
+ }
92
+ switch (Boolean(tz)) {
93
+ case true:
94
+ return moment(date)
95
+ .tz(tz)
96
+ .format(format);
97
+ default:
98
+ return moment.utc(date).format(format);
99
+ }
100
+ }
101
+ export function any_datetime_to_millis(firebaseTimestamp, options) {
102
+ const { defaultReturnedValue, fromFormat, tz, debug } = {
103
+ defaultReturnedValue: options?.defaultReturnedValue ?? 0,
104
+ fromFormat: options?.fromFormat ?? "DD/MM/YYYY HH:mm:ss",
105
+ tz: options?.tz,
106
+ debug: options?.debug ?? false,
107
+ };
108
+ switch (true) {
109
+ case !firebaseTimestamp: {
110
+ return defaultReturnedValue;
111
+ }
112
+ case firebaseTimestamp instanceof Timestamp: {
113
+ return firebaseTimestamp.toMillis();
114
+ }
115
+ case firebaseTimestamp instanceof Date: {
116
+ const ms = firebaseTimestamp.getTime();
117
+ return isNaN(ms) ? defaultReturnedValue : ms;
118
+ }
119
+ case typeof firebaseTimestamp === "string" || typeof firebaseTimestamp === "number": {
120
+ const m = moment.utc(firebaseTimestamp, fromFormat);
121
+ switch (m.isValid()) {
122
+ case true:
123
+ return m.valueOf();
124
+ default:
125
+ return defaultReturnedValue;
126
+ }
127
+ }
128
+ case !!firebaseTimestamp._seconds: {
129
+ const seconds = firebaseTimestamp._seconds;
130
+ const nanos = firebaseTimestamp._nanoseconds ?? 0;
131
+ return seconds * 1000 + Math.floor(nanos / 1000000);
132
+ }
133
+ case !!firebaseTimestamp.seconds: {
134
+ const seconds = firebaseTimestamp.seconds;
135
+ const nanos = firebaseTimestamp.nanoseconds ?? 0;
136
+ return seconds * 1000 + Math.floor(nanos / 1000000);
137
+ }
138
+ default: {
139
+ if (debug) {
140
+ console.error("Invalid timestamp format: ", firebaseTimestamp);
141
+ }
142
+ return defaultReturnedValue;
143
+ }
144
+ }
145
+ }
146
+ // old versions of the functions kept for backward compatibility
147
+ /**
148
+ * Converts a Firebase Timestamp object into a formatted string.
149
+ *
150
+ * @param {firebase_timestamp} firebaseTimestamp - The Firebase timestamp object containing _seconds and _nanoseconds.
151
+ * @param {string} [format="DD-MM-YYYY HH:mm:ss"] - Optional the format string used to format the date. Default is "DD-MM-YYYY HH:mm:ss".
152
+ * @returns {string} - A formatted date string according to the specified format or the default format.
153
+ */
154
+ export function timestamp_to_string(firebaseTimestamp, format = "DD-MM-YYYY HH:mm:ss") {
155
+ const timestamp = new Timestamp(firebaseTimestamp._seconds, firebaseTimestamp._nanoseconds);
156
+ return moment(timestamp.toDate()).utc().format(format);
157
+ }
158
+ /**
159
+ * Converts a Firebase Timestamp object into milliseconds since the Unix epoch.
160
+ *
161
+ * @param {firebase_timestamp} firebaseTimestamp - The Firebase timestamp object containing _seconds and _nanoseconds.
162
+ * @returns {number} - Time in milliseconds
163
+ */
164
+ export function timestamp_to_millis(firebaseTimestamp) {
165
+ const timestamp = new Timestamp(firebaseTimestamp._seconds, firebaseTimestamp._nanoseconds);
166
+ return timestamp.toMillis();
167
+ }
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import * as helpers from "./helpers";
2
+ import * as managers from "./managers";
3
+ import * as middlewares from "./middlewares";
4
+ import * as types from "./types";
5
+ export { helpers, managers, middlewares, types };
@@ -0,0 +1,37 @@
1
+ export class CacheManager {
2
+ static instance;
3
+ data = {};
4
+ constructor() { }
5
+ static getInstance() {
6
+ if (!CacheManager.instance) {
7
+ CacheManager.instance = new CacheManager();
8
+ }
9
+ return CacheManager.instance;
10
+ }
11
+ setArrayData(key, data) {
12
+ this.data[key] = data;
13
+ }
14
+ getArrayData(key) {
15
+ if (!this.data[key]) {
16
+ return [];
17
+ }
18
+ return this.data[key];
19
+ }
20
+ setObjectData(key, data) {
21
+ this.data[key] = data;
22
+ }
23
+ getObjectData(key, default_value = null) {
24
+ return this.data[key] || default_value;
25
+ }
26
+ getObjectDataAsArray(key, default_value = []) {
27
+ const value = this.data[key];
28
+ if (!value) {
29
+ return default_value;
30
+ }
31
+ if (Array.isArray(value)) {
32
+ return value;
33
+ }
34
+ return Object.values(value);
35
+ }
36
+ }
37
+ export const cache_manager = CacheManager.getInstance();
@@ -0,0 +1,4 @@
1
+ export * from "./cache_manager";
2
+ export * from "./translation_manager";
3
+ export * from "./logger_manager";
4
+ export * from "./rabbitmq_manager";
@@ -0,0 +1,68 @@
1
+ import moment_timezone from "moment-timezone";
2
+ import axios from "axios";
3
+ import { isObject } from "lodash";
4
+ import { parse_error } from "../helpers";
5
+ class LoggerManager {
6
+ static instance;
7
+ constructor() { }
8
+ static getInstance() {
9
+ if (!LoggerManager.instance) {
10
+ LoggerManager.instance = new LoggerManager();
11
+ }
12
+ return LoggerManager.instance;
13
+ }
14
+ get_date() {
15
+ return moment_timezone().tz("Asia/Jerusalem").format("DD/MM/YYYY HH:mm:ss.SS");
16
+ }
17
+ is_gcp() {
18
+ return !!(process.env.KUBERNETES_SERVICE_HOST || process.env.K_SERVICE);
19
+ }
20
+ emit(severity, text) {
21
+ if (this.is_gcp()) {
22
+ // GCP: structured JSON on stdout so Cloud Logging reads `severity`, not the stream
23
+ console.log(JSON.stringify({ severity, message: text }));
24
+ return;
25
+ }
26
+ const method = severity === "ERROR" ? console.error : severity === "WARNING" ? console.warn : console.log;
27
+ method(text);
28
+ }
29
+ log(msg, data) {
30
+ const is_table = !process.env.KUBERNETES_SERVICE_HOST &&
31
+ Array.isArray(data) &&
32
+ data.length > 1 &&
33
+ data.every((val) => {
34
+ if (typeof val === "object" && !Array.isArray(val)) {
35
+ return Object.values(val).every((v) => ["string", "number", "boolean"].includes(typeof v) || v === null);
36
+ }
37
+ return false;
38
+ }) &&
39
+ data.some((val) => Object.values(val).length > 1);
40
+ if (is_table) {
41
+ console.log(`${this.get_date()} - `, msg, ": ");
42
+ console.table(data);
43
+ return;
44
+ }
45
+ const text = `${this.get_date()} - ${msg}${data === undefined ? "" : `: ${isObject(data) || Array.isArray(data) ? JSON.stringify(data) : data}`}`;
46
+ this.emit("INFO", text);
47
+ }
48
+ error(msg, data) {
49
+ let text;
50
+ if (axios.isAxiosError(data)) {
51
+ if (!!data.response?.data) {
52
+ text = `${this.get_date()} - ${msg}, axios error: ${data.message}, data: ${JSON.stringify(data)}`;
53
+ }
54
+ else {
55
+ text = `${this.get_date()} - ${msg}, axios error: ${data.message}`;
56
+ }
57
+ }
58
+ else {
59
+ text = `${this.get_date()} - ${msg}${data === undefined ? "" : `: ${JSON.stringify(parse_error(data))}`}`;
60
+ }
61
+ this.emit("ERROR", text);
62
+ }
63
+ warn(msg, data) {
64
+ const text = `${this.get_date()} - ${msg}${data === undefined ? "" : `: ${JSON.stringify(data)}`}`;
65
+ this.emit("WARNING", text);
66
+ }
67
+ }
68
+ export const logger = LoggerManager.getInstance();
@@ -0,0 +1,179 @@
1
+ import amqp from "amqplib";
2
+ import { randomUUID as random_uuid } from "node:crypto";
3
+ import { logger } from "./logger_manager";
4
+ const SAFE_NAME = /^[A-Za-z0-9_.:-]{1,120}$/;
5
+ /**
6
+ * JSON event transport backed by durable fanout exchanges and durable queues.
7
+ *
8
+ * Each channel name maps to an exchange. Each distinct subscriber name gets a
9
+ * queue and therefore its own copy of every event. Instances sharing the same
10
+ * subscriber name compete for work, which supports horizontal scaling.
11
+ */
12
+ export class RabbitManager {
13
+ prefetch_count = 10;
14
+ connection;
15
+ channel;
16
+ connecting;
17
+ constructor() {
18
+ if (!(process.env.RABBITMQ_URL ?? "")) {
19
+ logger.warn("Missing ENV parameter RABBITMQ_URL");
20
+ }
21
+ }
22
+ async connect() {
23
+ if (this.connection && this.channel)
24
+ return;
25
+ this.connecting ??= this.open_connection().finally(() => {
26
+ this.connecting = undefined;
27
+ });
28
+ await this.connecting;
29
+ }
30
+ async close() {
31
+ const channel = this.channel;
32
+ const connection = this.connection;
33
+ this.channel = undefined;
34
+ this.connection = undefined;
35
+ if (channel)
36
+ await channel.close().catch(() => undefined);
37
+ if (connection)
38
+ await connection.close().catch(() => undefined);
39
+ }
40
+ async push_event(channel_name, payload, options = {}) {
41
+ const channel = await this.declare_exchange(channel_name);
42
+ const { ttl_seconds, headers = {} } = options;
43
+ if (ttl_seconds !== undefined && (!Number.isFinite(ttl_seconds) || ttl_seconds <= 0)) {
44
+ throw new Error("ttl_seconds must be a finite number greater than zero");
45
+ }
46
+ let encoded;
47
+ try {
48
+ encoded = JSON.stringify(payload);
49
+ }
50
+ catch (error) {
51
+ throw new Error("payload must be JSON serializable", { cause: error });
52
+ }
53
+ if (encoded === undefined)
54
+ throw new Error("payload must be JSON serializable");
55
+ const event_id = random_uuid();
56
+ const created_at = new Date().toISOString();
57
+ channel.publish(channel_name, "", Buffer.from(encoded, "utf8"), {
58
+ contentType: "application/json",
59
+ contentEncoding: "utf-8",
60
+ persistent: true,
61
+ messageId: event_id,
62
+ timestamp: Math.floor(Date.now() / 1_000),
63
+ ...(ttl_seconds === undefined ? {} : { expiration: String(Math.round(ttl_seconds * 1_000)) }),
64
+ headers: { created_at, ...headers },
65
+ });
66
+ await channel.waitForConfirms();
67
+ return event_id;
68
+ }
69
+ subscriptions = [];
70
+ async subscribe_for_channel(channel_name, subscriber_name, handler, options = {}) {
71
+ const { channel, queue_name } = await this.declare_subscription(channel_name, subscriber_name);
72
+ const requeue_on_error = options.requeue_on_error ?? true;
73
+ const reply = await channel.consume(queue_name, (message) => {
74
+ if (!message)
75
+ return;
76
+ void this.handle_message(channel, message, handler, requeue_on_error);
77
+ }, { noAck: false });
78
+ const subscription = {
79
+ queue_name,
80
+ consumer_tag: reply.consumerTag,
81
+ cancel: async () => {
82
+ await channel.cancel(reply.consumerTag);
83
+ },
84
+ };
85
+ this.subscriptions.push(subscription);
86
+ return subscription;
87
+ }
88
+ async cancel_subscriptions() {
89
+ await Promise.all(this.subscriptions.map((subscription) => subscription.cancel()));
90
+ }
91
+ async pull_and_delete(channel_name, subscriber_name) {
92
+ const { channel, queue_name } = await this.declare_subscription(channel_name, subscriber_name);
93
+ const message = await channel.get(queue_name, { noAck: false });
94
+ if (!message)
95
+ return undefined;
96
+ const event = this.to_event(message);
97
+ channel.ack(message);
98
+ return event;
99
+ }
100
+ /**
101
+ * Diagnostic peek implemented as get + nack + requeue.
102
+ * It is not a reliable queue browser: the same event can repeat or move.
103
+ */
104
+ async pull_and_leave(channel_name, subscriber_name) {
105
+ const { channel, queue_name } = await this.declare_subscription(channel_name, subscriber_name);
106
+ const message = await channel.get(queue_name, { noAck: false });
107
+ if (!message)
108
+ return undefined;
109
+ const event = this.to_event(message);
110
+ channel.nack(message, false, true);
111
+ return event;
112
+ }
113
+ async open_connection() {
114
+ const connection_url = new URL(process.env.RABBITMQ_URL);
115
+ if (!connection_url.searchParams.has("heartbeat")) {
116
+ connection_url.searchParams.set("heartbeat", "30");
117
+ }
118
+ const connection = await amqp.connect(connection_url.toString(), { timeout: 30_000 });
119
+ const channel = await connection.createConfirmChannel();
120
+ await channel.prefetch(this.prefetch_count);
121
+ this.connection = connection;
122
+ this.channel = channel;
123
+ }
124
+ async get_channel() {
125
+ await this.connect();
126
+ if (!this.channel)
127
+ throw new Error("RabbitMQ channel is not available");
128
+ return this.channel;
129
+ }
130
+ async declare_exchange(channel_name) {
131
+ this.validate_name(channel_name, "channel_name");
132
+ const channel = await this.get_channel();
133
+ await channel.assertExchange(channel_name, "fanout", { durable: true });
134
+ return channel;
135
+ }
136
+ async declare_subscription(channel_name, subscriber_name) {
137
+ this.validate_name(channel_name, "channel_name");
138
+ this.validate_name(subscriber_name, "subscriber_name");
139
+ const channel = await this.declare_exchange(channel_name);
140
+ const queue_name = `${channel_name}.${subscriber_name}`;
141
+ await channel.assertQueue(queue_name, { durable: true });
142
+ await channel.bindQueue(queue_name, channel_name, "");
143
+ return { channel, queue_name };
144
+ }
145
+ async handle_message(channel, message, handler, requeue_on_error) {
146
+ try {
147
+ await handler(this.to_event(message));
148
+ channel.ack(message);
149
+ }
150
+ catch (error) {
151
+ console.error(`RabbitMQ handler failed for ${message.properties.messageId ?? "unknown"}`, error);
152
+ channel.nack(message, false, requeue_on_error);
153
+ }
154
+ }
155
+ to_event(message) {
156
+ let body;
157
+ try {
158
+ body = JSON.parse(message.content.toString("utf8"));
159
+ }
160
+ catch (error) {
161
+ throw new Error("received a message that is not valid JSON", { cause: error });
162
+ }
163
+ const headers = { ...message.properties.headers };
164
+ const created_at = headers.created_at;
165
+ return {
166
+ body,
167
+ redelivered: message.fields.redelivered,
168
+ headers,
169
+ ...(message.properties.messageId ? { event_id: message.properties.messageId } : {}),
170
+ ...(typeof created_at === "string" ? { created_at } : {}),
171
+ };
172
+ }
173
+ validate_name(value, parameter) {
174
+ if (!SAFE_NAME.test(value)) {
175
+ throw new Error(`${parameter} must be 1-120 characters using letters, digits, _, ., :, or -`);
176
+ }
177
+ }
178
+ }
179
+ export const rabbitmq = new RabbitManager();
@@ -0,0 +1,38 @@
1
+ export class TranslationManager {
2
+ static instance;
3
+ data = {};
4
+ constructor() { }
5
+ static getInstance() {
6
+ if (!TranslationManager.instance) {
7
+ TranslationManager.instance = new TranslationManager();
8
+ }
9
+ return TranslationManager.instance;
10
+ }
11
+ setTranslationData(data) {
12
+ this.data = data;
13
+ }
14
+ getTranslationData() {
15
+ return this.data;
16
+ }
17
+ get_translation(scope, lang, entity, key) {
18
+ key = entity + (entity === "" ? "" : "__") + key;
19
+ return this.data[scope][lang][key] || key;
20
+ }
21
+ get_sms(lang, entity, key) {
22
+ try {
23
+ return this.data["sms"][lang][entity + "__" + key];
24
+ }
25
+ catch (error) {
26
+ return "N/A";
27
+ }
28
+ }
29
+ get_email(lang, entity, key) {
30
+ try {
31
+ return this.data["email"][lang][entity + "__" + key];
32
+ }
33
+ catch (error) {
34
+ return "N/A";
35
+ }
36
+ }
37
+ }
38
+ export const translation_manager = TranslationManager.getInstance();