@bikdotai/bik-shared-backend 20.3.1 → 20.3.2-beta.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/lib/alerts/templates/campaign/campaignMovedToDraftSegmentSyncDelay.d.ts +1 -0
- package/lib/alerts/templates/campaign/campaignMovedToDraftSegmentSyncDelay.js +4 -0
- package/lib/alerts/templates/campaign/campaignMovedToDraftSegmentSyncFailed.d.ts +1 -0
- package/lib/alerts/templates/campaign/campaignMovedToDraftSegmentSyncFailed.js +4 -0
- package/lib/alerts/templates/campaign/campaignScheduledTimeExceededDueToSyncDelay.d.ts +1 -0
- package/lib/alerts/templates/campaign/campaignScheduledTimeExceededDueToSyncDelay.js +4 -0
- package/lib/alertsV2/alertInstances.repo.js +26 -2
- package/lib/alertsV2/alertsV2.helper.d.ts +3 -4
- package/lib/alertsV2/alertsV2.helper.js +49 -3
- package/lib/alertsV2/alertsV2.service.js +14 -5
- package/lib/auth/authMiddlewares.js +68 -3
- package/lib/auth/firebase-auth.service.js +3 -3
- package/lib/auth/implementations/bik-admin-auth-service.d.ts +1 -1
- package/lib/auth/implementations/bik-admin-auth-service.js +2 -1
- package/lib/auth/index.d.ts +3 -0
- package/lib/auth/index.js +3 -0
- package/lib/auth/secret-manager/configManager.firestore.d.ts +22 -0
- package/lib/auth/secret-manager/configManager.firestore.js +166 -0
- package/lib/auth/secret-manager/configManager.helper.d.ts +13 -0
- package/lib/auth/secret-manager/configManager.helper.js +32 -0
- package/lib/auth/secret-manager/configManager.model.d.ts +38 -0
- package/lib/auth/secret-manager/configManager.model.js +2 -0
- package/lib/auth/secret-manager/configManager.service.d.ts +17 -0
- package/lib/auth/secret-manager/configManager.service.js +138 -0
- package/lib/auth/secret-manager/env-variables/variables.list.d.ts +3 -0
- package/lib/auth/secret-manager/env-variables/variables.list.js +3 -0
- package/lib/chat-handover-protocol/chat-handover-protocol.js +26 -41
- package/lib/core/local_runner.js +28 -11
- package/lib/core/setup.d.ts +3 -0
- package/lib/core/setup.js +24 -2
- package/lib/database/database.model.d.ts +95 -0
- package/lib/database/database.model.js +5 -0
- package/lib/database/database.service.d.ts +90 -0
- package/lib/database/database.service.js +382 -0
- package/lib/database/index.d.ts +7 -0
- package/lib/database/index.js +23 -0
- package/lib/elastic/counter/ingestion.js +1 -0
- package/lib/elastic/queries/campaign/getBroadcastDetailedStats.d.ts +117 -0
- package/lib/elastic/queries/campaign/getBroadcastDetailedStats.js +94 -1
- package/lib/elastic/queries/campaign/getUniqueCustomerCnt.d.ts +1 -0
- package/lib/elastic/queries/campaign/getUniqueCustomerCnt.js +1 -0
- package/lib/elastic/queries/chatbot/getAgentCostForBroadcast.d.ts +117 -0
- package/lib/elastic/queries/chatbot/getAgentCostForBroadcast.js +98 -0
- package/lib/elastic/queries/chatbot/getAiOperations.d.ts +5 -5
- package/lib/elastic/queries/chatbot/getAiOperations.js +3 -3
- package/lib/elastic/queries/chatbot/index.d.ts +1 -0
- package/lib/elastic/queries/chatbot/index.js +1 -0
- package/lib/elastic/queries/crm/getActivityTimelineByAgent.js +1 -1
- package/lib/elastic/queries/crm/getBreachedSLACount.d.ts +1 -0
- package/lib/elastic/queries/crm/getBreachedSLACount.js +8 -5
- package/lib/elastic/queries/crm/getFirstResponseTime.d.ts +1 -0
- package/lib/elastic/queries/crm/getFirstResponseTime.js +8 -5
- package/lib/elastic/queries/integrations/getOrdersShadowServices.d.ts +76 -0
- package/lib/elastic/queries/integrations/getOrdersShadowServices.js +61 -0
- package/lib/elastic/queries/integrations/index.d.ts +1 -0
- package/lib/elastic/queries/integrations/index.js +1 -0
- package/lib/elastic/queries/openAi/addToCartSession.d.ts +1 -0
- package/lib/elastic/queries/openAi/addToCartSession.js +11 -3
- package/lib/elastic/queries/openAi/checkoutCompletedSession.d.ts +1 -0
- package/lib/elastic/queries/openAi/checkoutCompletedSession.js +11 -3
- package/lib/elastic/reports/crm/index.d.ts +1 -0
- package/lib/elastic/reports/crm/index.js +1 -0
- package/lib/elastic/reports/reports.service.js +17 -8
- package/lib/events/events.d.ts +8 -0
- package/lib/events/events.js +25 -1
- package/lib/events/schema/events.helper.d.ts +1 -0
- package/lib/events/schema/events.helper.js +12 -6
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/merchant-events/elastic.search.d.ts +5 -0
- package/lib/merchant-events/elastic.search.js +23 -2
- package/lib/merchant-events/merchant.service.d.ts +1 -1
- package/lib/merchant-events/merchant.service.js +12 -11
- package/lib/recordAnalytics/recordAnalytics.service.js +5 -4
- package/lib/redis/redisPubSubService.d.ts +4 -6
- package/lib/redis/redisPubSubService.js +211 -123
- package/lib/redis/redisService.js +14 -8
- package/lib/swagger/SwaggerSchemaHelper.js +21 -17
- package/lib/user-properties/userProperties.service.js +1 -0
- package/package.json +2 -2
|
@@ -15,10 +15,99 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.RedisPubSubService = void 0;
|
|
16
16
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
17
17
|
const auth_1 = require("../auth");
|
|
18
|
+
// One publisher + one subscriber shared across ALL RedisPubSubService instances.
|
|
19
|
+
// This ensures the entire process uses exactly 2 pub/sub connections regardless
|
|
20
|
+
// of how many channels are subscribed to.
|
|
21
|
+
let sharedPublisher = null;
|
|
22
|
+
let sharedSubscriber = null;
|
|
23
|
+
let shutdownRegistered = false;
|
|
24
|
+
// Stored so closeAll() can remove the handlers and avoid accumulation across
|
|
25
|
+
// closeAll() + reinit cycles (e.g. in tests).
|
|
26
|
+
let signalHandler = null;
|
|
27
|
+
// Registry mapping channel name → message callback for all active subscriptions.
|
|
28
|
+
// A single 'message' listener on sharedSubscriber dispatches via this map,
|
|
29
|
+
// avoiding MaxListenersExceededWarning when many channels are subscribed.
|
|
30
|
+
const channelCallbacks = new Map();
|
|
31
|
+
// Guards registration of the shared 'ready' and 'message' listeners so they
|
|
32
|
+
// are added to sharedSubscriber exactly once regardless of how many instances
|
|
33
|
+
// call subscribe().
|
|
34
|
+
let sharedListenersRegistered = false;
|
|
35
|
+
const MAX_RETRIES = 3;
|
|
36
|
+
const BASE_DELAY_MS = 200;
|
|
37
|
+
// Single authoritative predicate for transient Redis errors, used by both the
|
|
38
|
+
// client error handler and retryWithBackoff — no duplicate classification logic.
|
|
39
|
+
function isTransientError(err) {
|
|
40
|
+
const code = err === null || err === void 0 ? void 0 : err.code;
|
|
41
|
+
const name = err === null || err === void 0 ? void 0 : err.name;
|
|
42
|
+
const message = err === null || err === void 0 ? void 0 : err.message;
|
|
43
|
+
const transientCodes = new Set([
|
|
44
|
+
'ECONNRESET',
|
|
45
|
+
'ETIMEDOUT',
|
|
46
|
+
'EHOSTUNREACH',
|
|
47
|
+
'ENETUNREACH',
|
|
48
|
+
'ECONNREFUSED',
|
|
49
|
+
'NR_CLOSED',
|
|
50
|
+
'EPIPE',
|
|
51
|
+
]);
|
|
52
|
+
if (name === 'TimeoutError')
|
|
53
|
+
return true;
|
|
54
|
+
if (code && transientCodes.has(code))
|
|
55
|
+
return true;
|
|
56
|
+
if (message) {
|
|
57
|
+
return [
|
|
58
|
+
/connection is closed/i,
|
|
59
|
+
/socket closed/i,
|
|
60
|
+
/not yet established/i,
|
|
61
|
+
/still connecting/i,
|
|
62
|
+
/Ready check failed/i,
|
|
63
|
+
].some((r) => r.test(message));
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function createRedisClient(opts) {
|
|
68
|
+
var _a, _b, _c;
|
|
69
|
+
const client = new ioredis_1.default({
|
|
70
|
+
host: opts.host,
|
|
71
|
+
password: opts.password,
|
|
72
|
+
port: opts.port,
|
|
73
|
+
keepAlive: 30000,
|
|
74
|
+
lazyConnect: (_a = opts.lazyConnect) !== null && _a !== void 0 ? _a : false,
|
|
75
|
+
autoResubscribe: (_b = opts.autoResubscribe) !== null && _b !== void 0 ? _b : true,
|
|
76
|
+
enableReadyCheck: (_c = opts.enableReadyCheck) !== null && _c !== void 0 ? _c : true,
|
|
77
|
+
});
|
|
78
|
+
client.on('error', (err) => {
|
|
79
|
+
if (isTransientError(err))
|
|
80
|
+
return;
|
|
81
|
+
console.error('RedisPubSubService client error:', err);
|
|
82
|
+
});
|
|
83
|
+
return client;
|
|
84
|
+
}
|
|
85
|
+
// Module-level so the shared 'ready' listener (registered once) can call it
|
|
86
|
+
// without requiring a class instance reference.
|
|
87
|
+
function retryWithBackoff(fn) {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
let attempt = 0;
|
|
90
|
+
let lastError;
|
|
91
|
+
while (attempt <= MAX_RETRIES) {
|
|
92
|
+
try {
|
|
93
|
+
return yield fn();
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
lastError = err;
|
|
97
|
+
if (!isTransientError(err) || attempt === MAX_RETRIES) {
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, attempt), 2000);
|
|
101
|
+
yield new Promise((res) => setTimeout(res, delay));
|
|
102
|
+
attempt++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw lastError;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
18
108
|
class RedisPubSubService {
|
|
19
109
|
constructor(config) {
|
|
20
|
-
this.
|
|
21
|
-
this.baseDelayMs = 200;
|
|
110
|
+
this.subscribed = false;
|
|
22
111
|
const host = auth_1.EnvVariableHelper.access(auth_1.ServiceName.REDIS, auth_1.RedisKeys.HOST, 'REDIS_HOST') || 'localhost';
|
|
23
112
|
const portStr = auth_1.EnvVariableHelper.access(auth_1.ServiceName.REDIS, auth_1.RedisKeys.PORT, 'REDIS_PORT');
|
|
24
113
|
const port = portStr ? parseInt(portStr) : 6379;
|
|
@@ -27,114 +116,60 @@ class RedisPubSubService {
|
|
|
27
116
|
if (!host || !password) {
|
|
28
117
|
throw new Error('Missing environment variables for Redis Cloud from bik-shared-backend');
|
|
29
118
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// our SUBSCRIBE and producing the same "subscriber mode" error.
|
|
36
|
-
this.subscriber = new ioredis_1.default({
|
|
37
|
-
host,
|
|
38
|
-
password,
|
|
39
|
-
port,
|
|
40
|
-
autoResubscribe: false,
|
|
41
|
-
enableReadyCheck: false,
|
|
42
|
-
});
|
|
43
|
-
const isTransient = (err) => {
|
|
44
|
-
const code = err === null || err === void 0 ? void 0 : err.code;
|
|
45
|
-
const name = err === null || err === void 0 ? void 0 : err.name;
|
|
46
|
-
const message = err === null || err === void 0 ? void 0 : err.message;
|
|
47
|
-
const transientCodes = new Set([
|
|
48
|
-
'ECONNRESET',
|
|
49
|
-
'ETIMEDOUT',
|
|
50
|
-
'EHOSTUNREACH',
|
|
51
|
-
'ENETUNREACH',
|
|
52
|
-
'ECONNREFUSED',
|
|
53
|
-
'NR_CLOSED',
|
|
54
|
-
'EPIPE',
|
|
55
|
-
]);
|
|
56
|
-
if (name === 'TimeoutError')
|
|
57
|
-
return true;
|
|
58
|
-
if (code && transientCodes.has(code))
|
|
59
|
-
return true;
|
|
60
|
-
if (message) {
|
|
61
|
-
return [
|
|
62
|
-
/connection is closed/i,
|
|
63
|
-
/socket closed/i,
|
|
64
|
-
/not yet established/i,
|
|
65
|
-
/still connecting/i,
|
|
66
|
-
/Ready check failed/i,
|
|
67
|
-
].some((r) => r.test(message));
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
};
|
|
71
|
-
this.publisher.on('error', (err) => {
|
|
72
|
-
if (isTransient(err))
|
|
73
|
-
return; // suppress transient errors
|
|
74
|
-
console.error('Redis publisher error:', err);
|
|
75
|
-
});
|
|
76
|
-
this.subscriber.on('error', (err) => {
|
|
77
|
-
if (isTransient(err))
|
|
78
|
-
return; // suppress transient errors
|
|
79
|
-
console.error('Redis subscriber error:', err);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
isRetriableRedisError(error) {
|
|
83
|
-
const code = error === null || error === void 0 ? void 0 : error.code;
|
|
84
|
-
const name = error === null || error === void 0 ? void 0 : error.name;
|
|
85
|
-
const message = error === null || error === void 0 ? void 0 : error.message;
|
|
86
|
-
const transientCodes = new Set([
|
|
87
|
-
'ECONNRESET',
|
|
88
|
-
'ETIMEDOUT',
|
|
89
|
-
'EHOSTUNREACH',
|
|
90
|
-
'ENETUNREACH',
|
|
91
|
-
'ECONNREFUSED',
|
|
92
|
-
'NR_CLOSED',
|
|
93
|
-
'EPIPE',
|
|
94
|
-
]);
|
|
95
|
-
if (name === 'TimeoutError')
|
|
96
|
-
return true;
|
|
97
|
-
if (code && transientCodes.has(code))
|
|
98
|
-
return true;
|
|
99
|
-
if (message) {
|
|
100
|
-
const patterns = [
|
|
101
|
-
/connection is closed/i,
|
|
102
|
-
/socket closed/i,
|
|
103
|
-
/The connection is not yet established/i,
|
|
104
|
-
/ioredis is still connecting/i,
|
|
105
|
-
/Ready check failed/i,
|
|
106
|
-
];
|
|
107
|
-
if (patterns.some((re) => re.test(message)))
|
|
108
|
-
return true;
|
|
119
|
+
// Initialise shared clients once per process.
|
|
120
|
+
if (!sharedPublisher) {
|
|
121
|
+
// lazyConnect: true — publisher only opens a connection when publish() is
|
|
122
|
+
// first called, saving a connection on pods that only subscribe.
|
|
123
|
+
sharedPublisher = createRedisClient({ host, password, port, lazyConnect: true });
|
|
109
124
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
if (!sharedSubscriber) {
|
|
126
|
+
// autoResubscribe: false — we re-subscribe manually in the 'ready' handler
|
|
127
|
+
// so ioredis's built-in re-subscribe doesn't double-up and cause the
|
|
128
|
+
// "Connection in subscriber mode" error on reconnect.
|
|
129
|
+
// enableReadyCheck: false — avoids the INFO ready-check command racing with
|
|
130
|
+
// SUBSCRIBE on reconnect.
|
|
131
|
+
sharedSubscriber = createRedisClient({
|
|
132
|
+
host,
|
|
133
|
+
password,
|
|
134
|
+
port,
|
|
135
|
+
autoResubscribe: false,
|
|
136
|
+
enableReadyCheck: false,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Register SIGTERM/SIGINT cleanup once per process. This handler closes the
|
|
140
|
+
// shared Redis connections gracefully. process.exit() is intentionally NOT
|
|
141
|
+
// called here — that responsibility belongs to the host service (e.g. via
|
|
142
|
+
// Fastify server.close() in main.ts) so it can drain HTTP and perform its
|
|
143
|
+
// own cleanup before exiting.
|
|
144
|
+
// signalHandler is stored so closeAll() can remove it via process.off(),
|
|
145
|
+
// preventing handler accumulation across closeAll() + reinit cycles.
|
|
146
|
+
if (!shutdownRegistered) {
|
|
147
|
+
shutdownRegistered = true;
|
|
148
|
+
signalHandler = () => __awaiter(this, void 0, void 0, function* () {
|
|
149
|
+
console.log('[RedisPubSubService] Signal received — closing shared Redis pub/sub connections');
|
|
117
150
|
try {
|
|
118
|
-
|
|
151
|
+
yield Promise.all([sharedPublisher === null || sharedPublisher === void 0 ? void 0 : sharedPublisher.quit(), sharedSubscriber === null || sharedSubscriber === void 0 ? void 0 : sharedSubscriber.quit()]);
|
|
152
|
+
console.log('[RedisPubSubService] Redis pub/sub connections closed successfully');
|
|
119
153
|
}
|
|
120
154
|
catch (err) {
|
|
121
|
-
|
|
122
|
-
if (!this.isRetriableRedisError(err) || attempt === this.maxRetries) {
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
const delay = Math.min(this.baseDelayMs * Math.pow(2, attempt), 2000);
|
|
126
|
-
yield new Promise((res) => setTimeout(res, delay));
|
|
127
|
-
attempt++;
|
|
155
|
+
console.warn('[RedisPubSubService] Error closing pub/sub connections:', err);
|
|
128
156
|
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
157
|
+
});
|
|
158
|
+
process.on('SIGTERM', signalHandler);
|
|
159
|
+
process.on('SIGINT', signalHandler);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
get publisher() {
|
|
163
|
+
return sharedPublisher;
|
|
164
|
+
}
|
|
165
|
+
get subscriber() {
|
|
166
|
+
return sharedSubscriber;
|
|
132
167
|
}
|
|
133
168
|
publish(data) {
|
|
134
169
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
170
|
try {
|
|
136
171
|
const payload = JSON.stringify(data);
|
|
137
|
-
yield
|
|
172
|
+
yield retryWithBackoff(() => this.publisher.publish(this.channel, payload));
|
|
138
173
|
console.log(`Published to ${this.channel}:`, data);
|
|
139
174
|
}
|
|
140
175
|
catch (error) {
|
|
@@ -143,39 +178,92 @@ class RedisPubSubService {
|
|
|
143
178
|
});
|
|
144
179
|
}
|
|
145
180
|
subscribe(callback) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
181
|
+
if (this.subscribed) {
|
|
182
|
+
console.warn(`RedisPubSubService: already subscribed to channel "${this.channel}", ignoring duplicate call.`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.subscribed = true;
|
|
186
|
+
// Register the callback before attaching listeners so the 'ready' handler
|
|
187
|
+
// sees this channel immediately on the first connect.
|
|
188
|
+
channelCallbacks.set(this.channel, callback);
|
|
189
|
+
if (!sharedListenersRegistered) {
|
|
190
|
+
sharedListenersRegistered = true;
|
|
191
|
+
// Single 'ready' listener re-subscribes ALL registered channels on every
|
|
192
|
+
// connect/reconnect. autoResubscribe is disabled so we own this entirely.
|
|
193
|
+
// 'ready' fires after AUTH + SELECT, safe to send SUBSCRIBE here.
|
|
194
|
+
sharedSubscriber.on('ready', () => {
|
|
195
|
+
const channels = Array.from(channelCallbacks.keys());
|
|
196
|
+
if (channels.length === 0)
|
|
197
|
+
return;
|
|
198
|
+
console.log(`[RedisPubSubService] Subscriber ready — resubscribing to: ${channels.join(', ')}`);
|
|
199
|
+
retryWithBackoff(() => sharedSubscriber.subscribe(...channels))
|
|
200
|
+
.then(() => console.log(`[RedisPubSubService] Subscribed to: ${channels.join(', ')}`))
|
|
201
|
+
.catch((err) => console.error('[RedisPubSubService] Subscription failed:', err));
|
|
202
|
+
});
|
|
203
|
+
// Single 'message' listener dispatches to the correct per-channel callback
|
|
204
|
+
// via the registry. This replaces per-instance listeners and prevents
|
|
205
|
+
// MaxListenersExceededWarning regardless of how many channels are active.
|
|
206
|
+
sharedSubscriber.on('message', (incomingChannel, message) => {
|
|
207
|
+
const cb = channelCallbacks.get(incomingChannel);
|
|
208
|
+
if (!cb)
|
|
209
|
+
return;
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(message);
|
|
212
|
+
console.log(`Message received on ${incomingChannel}:`, parsed);
|
|
213
|
+
cb(parsed);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
console.error(`[RedisPubSubService] Error parsing message on ${incomingChannel}:`, e);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
159
220
|
// Handle the case where the connection is already established before
|
|
160
221
|
// subscribe() is called (e.g. delayed initialization).
|
|
161
222
|
if (this.subscriber.status === 'ready') {
|
|
162
|
-
|
|
223
|
+
retryWithBackoff(() => this.subscriber.subscribe(this.channel))
|
|
224
|
+
.then(() => console.log(`[RedisPubSubService] Subscribed to channel: ${this.channel}`))
|
|
225
|
+
.catch((err) => console.error('[RedisPubSubService] Subscription failed:', err));
|
|
163
226
|
}
|
|
164
|
-
|
|
227
|
+
}
|
|
228
|
+
// Unsubscribes this instance's channel and removes its callback from the
|
|
229
|
+
// shared registry. The shared publisher/subscriber remain open for other
|
|
230
|
+
// active channels and are closed on SIGTERM or via closeAll().
|
|
231
|
+
close() {
|
|
232
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
233
|
+
if (!this.subscribed)
|
|
234
|
+
return;
|
|
235
|
+
this.subscribed = false;
|
|
236
|
+
channelCallbacks.delete(this.channel);
|
|
165
237
|
try {
|
|
166
|
-
|
|
167
|
-
console.log(`Message received on ${this.channel}:`, parsed);
|
|
168
|
-
callback(parsed);
|
|
238
|
+
yield (sharedSubscriber === null || sharedSubscriber === void 0 ? void 0 : sharedSubscriber.unsubscribe(this.channel));
|
|
169
239
|
}
|
|
170
|
-
catch (
|
|
171
|
-
console.
|
|
240
|
+
catch (err) {
|
|
241
|
+
console.warn(`[RedisPubSubService] Error unsubscribing from ${this.channel}:`, err);
|
|
172
242
|
}
|
|
173
243
|
});
|
|
174
244
|
}
|
|
175
|
-
|
|
245
|
+
// Closes both shared connections and resets all module-level state including
|
|
246
|
+
// signal handler registration. Intended for tests and explicit full teardown;
|
|
247
|
+
// in production the SIGTERM handler handles shutdown automatically.
|
|
248
|
+
static closeAll() {
|
|
176
249
|
return __awaiter(this, void 0, void 0, function* () {
|
|
177
|
-
|
|
178
|
-
|
|
250
|
+
try {
|
|
251
|
+
yield Promise.all([sharedPublisher === null || sharedPublisher === void 0 ? void 0 : sharedPublisher.quit(), sharedSubscriber === null || sharedSubscriber === void 0 ? void 0 : sharedSubscriber.quit()]);
|
|
252
|
+
sharedPublisher = null;
|
|
253
|
+
sharedSubscriber = null;
|
|
254
|
+
channelCallbacks.clear();
|
|
255
|
+
sharedListenersRegistered = false;
|
|
256
|
+
if (signalHandler) {
|
|
257
|
+
process.off('SIGTERM', signalHandler);
|
|
258
|
+
process.off('SIGINT', signalHandler);
|
|
259
|
+
signalHandler = null;
|
|
260
|
+
}
|
|
261
|
+
shutdownRegistered = false;
|
|
262
|
+
console.log('[RedisPubSubService] All Redis pub/sub connections closed.');
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
console.warn('[RedisPubSubService] Error closing Redis pub/sub connections:', err);
|
|
266
|
+
}
|
|
179
267
|
});
|
|
180
268
|
}
|
|
181
269
|
}
|
|
@@ -46,12 +46,13 @@ class RedisAppService {
|
|
|
46
46
|
host: redisHost,
|
|
47
47
|
password: redisKey,
|
|
48
48
|
port: redisPort ? Number(redisPort) : 6379,
|
|
49
|
-
keepAlive:
|
|
49
|
+
keepAlive: 30000,
|
|
50
50
|
maxRetriesPerRequest: null,
|
|
51
51
|
enableReadyCheck: false,
|
|
52
52
|
connectionName: logger_1.LOGGING_CONFIGURATION.serviceName,
|
|
53
53
|
connectTimeout: 5000,
|
|
54
54
|
commandTimeout: 5000,
|
|
55
|
+
disconnectTimeout: 10000,
|
|
55
56
|
reconnectOnError: (err) => {
|
|
56
57
|
if (err.code === 'ECONNREFUSED') {
|
|
57
58
|
console.error('REDIS QUOTA LIKELY EXCEEDED - Check Redis Labs dashboard', err);
|
|
@@ -138,7 +139,7 @@ class RedisAppService {
|
|
|
138
139
|
return __awaiter(this, void 0, void 0, function* () {
|
|
139
140
|
let attempt = 0;
|
|
140
141
|
let lastError;
|
|
141
|
-
while (attempt
|
|
142
|
+
while (attempt <= this.maxRetries) {
|
|
142
143
|
try {
|
|
143
144
|
return yield fn();
|
|
144
145
|
}
|
|
@@ -565,15 +566,20 @@ class RedisAppService {
|
|
|
565
566
|
exports.RedisAppService = RedisAppService;
|
|
566
567
|
// Dangerous - this will impact any service that bik shared is injected with.
|
|
567
568
|
const cleanup = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
569
|
+
console.log('[RedisAppService] SIGTERM received — closing Redis connection');
|
|
568
570
|
if (!RedisAppService.getInstance().getClient()) {
|
|
571
|
+
console.log('[RedisAppService] No client found, skipping');
|
|
569
572
|
return;
|
|
570
573
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
574
|
+
try {
|
|
575
|
+
yield RedisAppService.getInstance().gracefulShutdown();
|
|
576
|
+
console.log('[RedisAppService] Redis connection closed successfully');
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
console.warn('[RedisAppService] Error closing Redis connection:', err);
|
|
580
|
+
}
|
|
576
581
|
});
|
|
577
582
|
// Register the cleanup function for SIGTERM signal
|
|
578
583
|
process.on('SIGTERM', cleanup);
|
|
579
|
-
|
|
584
|
+
// Also handle SIGINT (Ctrl+C / local dev)
|
|
585
|
+
process.on('SIGINT', cleanup);
|
|
@@ -52,7 +52,11 @@ class SwaggerSchemaHelper {
|
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
54
|
// Convert camelCase or kebab-case to human-readable format
|
|
55
|
-
return prefix.charAt(0).toUpperCase() +
|
|
55
|
+
return (prefix.charAt(0).toUpperCase() +
|
|
56
|
+
prefix
|
|
57
|
+
.slice(1)
|
|
58
|
+
.replace(/([A-Z])/g, ' $1')
|
|
59
|
+
.trim());
|
|
56
60
|
}
|
|
57
61
|
static loadSwaggerConfig(apiPath) {
|
|
58
62
|
try {
|
|
@@ -90,10 +94,10 @@ class SwaggerSchemaHelper {
|
|
|
90
94
|
security: [
|
|
91
95
|
{
|
|
92
96
|
Authorization: [],
|
|
93
|
-
bikReferer: []
|
|
94
|
-
}
|
|
97
|
+
bikReferer: [],
|
|
98
|
+
},
|
|
95
99
|
],
|
|
96
|
-
'x-timeout': existingSchema === null || existingSchema === void 0 ? void 0 : existingSchema['x-timeout']
|
|
100
|
+
'x-timeout': existingSchema === null || existingSchema === void 0 ? void 0 : existingSchema['x-timeout'],
|
|
97
101
|
};
|
|
98
102
|
if (swaggerConfig['x-timeout']) {
|
|
99
103
|
swaggerConfig.description += `\n\n**Timeout:** ${swaggerConfig['x-timeout']} seconds`;
|
|
@@ -103,23 +107,23 @@ class SwaggerSchemaHelper {
|
|
|
103
107
|
acc[example.summary] = { value: example.value };
|
|
104
108
|
return acc;
|
|
105
109
|
}, {});
|
|
106
|
-
swaggerConfig[
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
swaggerConfig['responses'] = {
|
|
111
|
+
'200': {
|
|
112
|
+
description: 'Default Response',
|
|
109
113
|
},
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
'408': {
|
|
115
|
+
description: `Request Timeout - Operation exceeded ${swaggerConfig['x-timeout'] || 'configured'} seconds`,
|
|
112
116
|
},
|
|
113
117
|
};
|
|
114
|
-
swaggerConfig[
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
swaggerConfig['requestBody'] = {
|
|
119
|
+
content: {
|
|
120
|
+
'application/json': {
|
|
121
|
+
schema: {
|
|
122
|
+
type: 'object',
|
|
119
123
|
},
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
124
|
+
examples: flattenedExamples,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
123
127
|
};
|
|
124
128
|
return swaggerConfig;
|
|
125
129
|
}
|
|
@@ -111,6 +111,7 @@ class BikUserPropertiesService {
|
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
const userPropertiesPayload = userPropertiesHelper.constructUserProperties(currObj, (customerTagsMap === null || customerTagsMap === void 0 ? void 0 : customerTagsMap[currObj.customerId.toString()]) || [], undefined, currObj.tagsToRemove);
|
|
114
|
+
console.log('Final userPropertiesPayload:', JSON.stringify(userPropertiesPayload === null || userPropertiesPayload === void 0 ? void 0 : userPropertiesPayload.data, null, 2));
|
|
114
115
|
const isUatStore = (0, events_1.getUatStoreIds)().includes(currObj.storeId || storeId || '');
|
|
115
116
|
const upTopic = isUatStore ? 'trackUserProperty-uat' : 'trackUserProperty';
|
|
116
117
|
return (0, pubsub_listener_1.publishMessageToTopic)(upTopic, userPropertiesPayload === null || userPropertiesPayload === void 0 ? void 0 : userPropertiesPayload.data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bikdotai/bik-shared-backend",
|
|
3
|
-
"version": "20.3.
|
|
3
|
+
"version": "20.3.2-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@amplitude/identify": "^1.10.0",
|
|
37
37
|
"@amplitude/node": "^1.10.0",
|
|
38
|
-
"@bikdotai/bik-models": "
|
|
38
|
+
"@bikdotai/bik-models": "11.6.1-beta.0",
|
|
39
39
|
"@elastic/elasticsearch": "^8.7.0",
|
|
40
40
|
"@fastify/cookie": "^9.4.0",
|
|
41
41
|
"@fastify/cors": "^9.0.1",
|