@infuro/cms-core 1.0.47 → 1.0.51
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/README.md +30 -16
- package/dist/admin.cjs +2012 -437
- package/dist/admin.d.cts +13 -1
- package/dist/admin.d.ts +13 -1
- package/dist/admin.js +2013 -438
- package/dist/api.cjs +37 -37
- package/dist/api.js +5 -5
- package/dist/auth.cjs +52 -23
- package/dist/auth.d.cts +101 -2
- package/dist/auth.d.ts +101 -2
- package/dist/auth.js +3 -2
- package/dist/chunk-3EOM4V2M.cjs +452 -0
- package/dist/{chunk-2HU5R2JE.js → chunk-AYERBA7I.js} +1 -7
- package/dist/{chunk-YXH2UUEZ.js → chunk-CTXBYO2J.js} +290 -59
- package/dist/{chunk-LMJ7RKPF.cjs → chunk-JN4AFHZ4.cjs} +311 -59
- package/dist/{chunk-JE22VP6S.js → chunk-MXIWUFBP.js} +1 -1
- package/dist/{chunk-W42UZLQO.js → chunk-NUXR7BUG.js} +2819 -2597
- package/dist/chunk-RK5ETF2I.js +434 -0
- package/dist/{chunk-4PMK3RNA.cjs → chunk-TJ2MIQUT.cjs} +1 -7
- package/dist/{chunk-KOTXDSQB.cjs → chunk-UQWZUZ5X.cjs} +1 -1
- package/dist/{chunk-YC4NUZCS.js → chunk-WRKV6MHB.js} +414 -2
- package/dist/{chunk-XUCKZPML.cjs → chunk-YOW457RA.cjs} +2872 -2657
- package/dist/{chunk-Q3HQEM4R.cjs → chunk-YV5PK4JW.cjs} +421 -1
- package/dist/cli.cjs +13 -22
- package/dist/cli.js +13 -22
- package/dist/{emit-order-notification-trigger-6XX7LSC4.js → emit-order-notification-trigger-HZQPPSFB.js} +1 -1
- package/dist/{emit-order-notification-trigger-NASG7ZEO.cjs → emit-order-notification-trigger-NR3VN6C6.cjs} +3 -3
- package/dist/{event-order-defaults-7Z3UZOEH.cjs → event-order-defaults-DU7V3YND.cjs} +9 -9
- package/dist/{event-order-defaults-XQ3IDPD7.js → event-order-defaults-H4RT7NMR.js} +1 -1
- package/dist/index.cjs +379 -309
- package/dist/index.d.cts +136 -6
- package/dist/index.d.ts +136 -6
- package/dist/index.js +58 -24
- package/dist/migrations/1782400000000-CreateUserDeviceTokens.ts +36 -0
- package/dist/migrations/1782500000000-AddPushToOrderNotificationBindingsChannelEnum.ts +15 -0
- package/dist/{order-notification-dispatcher-6WNG24NY.js → order-notification-dispatcher-AT4IFAGL.js} +1 -1
- package/dist/{order-notification-dispatcher-6XSU3AR7.cjs → order-notification-dispatcher-YBAWDOIO.cjs} +7 -3
- package/package.json +1 -1
- package/dist/chunk-VUQFARRT.cjs +0 -182
- package/dist/chunk-ZF2RQWXB.js +0 -171
|
@@ -6,6 +6,7 @@ import { __name } from './chunk-SHUYVCID.js';
|
|
|
6
6
|
import { SESClient, SendRawEmailCommand, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import nodemailer from 'nodemailer';
|
|
9
|
+
import * as crypto from 'crypto';
|
|
9
10
|
|
|
10
11
|
// src/lib/event-order-template-context.ts
|
|
11
12
|
function resolvePublicSiteUrl() {
|
|
@@ -26,12 +27,6 @@ function buildOrderTrackUrl(qrToken, siteUrl, orderNumber) {
|
|
|
26
27
|
return `${base}/track/${token}`;
|
|
27
28
|
}
|
|
28
29
|
__name(buildOrderTrackUrl, "buildOrderTrackUrl");
|
|
29
|
-
function buildOrderQrImageUrl(trackUrl, size = 200, fallbackData) {
|
|
30
|
-
const data = trackUrl.trim() || fallbackData?.trim() || "";
|
|
31
|
-
if (!data) return "";
|
|
32
|
-
return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(data)}`;
|
|
33
|
-
}
|
|
34
|
-
__name(buildOrderQrImageUrl, "buildOrderQrImageUrl");
|
|
35
30
|
function formatOrderDetailsText(input) {
|
|
36
31
|
const { orderNumber, total, currency, lines } = input;
|
|
37
32
|
const header = `Order: ${orderNumber}
|
|
@@ -53,8 +48,6 @@ __name(formatOrderDetailsHtml, "formatOrderDetailsHtml");
|
|
|
53
48
|
function buildEventOrderTemplateVariables(input) {
|
|
54
49
|
const { event, order, contact, productNames, vendorName = "", orderLines = [], siteUrl } = input;
|
|
55
50
|
const trackUrl = buildOrderTrackUrl(order.qrToken, siteUrl, order.orderNumber);
|
|
56
|
-
const qrImageUrl = buildOrderQrImageUrl(trackUrl, 200, order.orderNumber);
|
|
57
|
-
const ticketQr = qrImageUrl ? `<img src="${qrImageUrl}" alt="Ticket QR Code" width="200" height="200" style="display:block;margin:12px 0;border:1px solid #e5e7eb;border-radius:8px;" />` : "";
|
|
58
51
|
const baseSite = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
|
|
59
52
|
const invoiceNumber = `INV-${order.orderNumber ?? ""}`;
|
|
60
53
|
const invoiceUrl = baseSite ? `${baseSite}/api/orders/${order.orderNumber ?? ""}/invoice` : `/api/orders/${order.orderNumber ?? ""}/invoice`;
|
|
@@ -64,6 +57,9 @@ function buildEventOrderTemplateVariables(input) {
|
|
|
64
57
|
currency: String(order.currency ?? "INR"),
|
|
65
58
|
lines: orderLines
|
|
66
59
|
};
|
|
60
|
+
const qrToken = order.qrToken?.trim() || order.orderNumber?.trim() || "";
|
|
61
|
+
const fallbackQrUrl = qrToken ? `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrToken)}` : "";
|
|
62
|
+
const fallbackTicketQr = fallbackQrUrl ? `<img src="${fallbackQrUrl}" alt="Ticket QR Code" width="200" height="200" style="display:block;margin:12px 0;border:1px solid #e5e7eb;border-radius:8px;" />` : "";
|
|
67
63
|
return {
|
|
68
64
|
eventName: event.name ?? productNames ?? "",
|
|
69
65
|
vendorName: vendorName.trim(),
|
|
@@ -78,10 +74,10 @@ function buildEventOrderTemplateVariables(input) {
|
|
|
78
74
|
customerPhone: contact.phone?.trim() || "",
|
|
79
75
|
productNames,
|
|
80
76
|
trackUrl,
|
|
81
|
-
qrImageUrl,
|
|
82
|
-
ticketQr,
|
|
83
77
|
invoiceNumber,
|
|
84
78
|
invoiceUrl,
|
|
79
|
+
ticketQr: fallbackTicketQr,
|
|
80
|
+
qrImageUrl: fallbackQrUrl,
|
|
85
81
|
orderDetails: formatOrderDetailsText(orderDetailsPayload),
|
|
86
82
|
orderDetailsHtml: formatOrderDetailsHtml(orderDetailsPayload)
|
|
87
83
|
};
|
|
@@ -497,7 +493,7 @@ var WhatsAppService = class {
|
|
|
497
493
|
let body = opts.body?.trim() ?? "";
|
|
498
494
|
const variables = opts.variables ?? {};
|
|
499
495
|
if (opts.templateKey?.trim() && this.getMessageTemplateRow) {
|
|
500
|
-
const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-
|
|
496
|
+
const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-H4RT7NMR.js');
|
|
501
497
|
if (opts.templateKey.trim() === EVENT_ORDER_TEMPLATE_KEY) {
|
|
502
498
|
const resolved = await resolveEventOrderTemplate("whatsapp", async (ch, key) => {
|
|
503
499
|
const row = await this.getMessageTemplateRow(ch, key);
|
|
@@ -2061,6 +2057,117 @@ function emailPlugin(config) {
|
|
|
2061
2057
|
};
|
|
2062
2058
|
}
|
|
2063
2059
|
__name(emailPlugin, "emailPlugin");
|
|
2060
|
+
function base64UrlEncode(str) {
|
|
2061
|
+
const buf = typeof str === "string" ? Buffer.from(str, "utf8") : str;
|
|
2062
|
+
return buf.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2063
|
+
}
|
|
2064
|
+
__name(base64UrlEncode, "base64UrlEncode");
|
|
2065
|
+
async function getGoogleAccessToken(clientEmail, privateKey) {
|
|
2066
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2067
|
+
const header = {
|
|
2068
|
+
alg: "RS256",
|
|
2069
|
+
typ: "JWT"
|
|
2070
|
+
};
|
|
2071
|
+
const claimSet = {
|
|
2072
|
+
iss: clientEmail,
|
|
2073
|
+
scope: "https://www.googleapis.com/auth/firebase.messaging",
|
|
2074
|
+
aud: "https://oauth2.googleapis.com/token",
|
|
2075
|
+
exp: now + 3600,
|
|
2076
|
+
iat: now
|
|
2077
|
+
};
|
|
2078
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
2079
|
+
const encodedClaimSet = base64UrlEncode(JSON.stringify(claimSet));
|
|
2080
|
+
const signatureInput = `${encodedHeader}.${encodedClaimSet}`;
|
|
2081
|
+
const formattedKey = privateKey.replace(/\\n/g, "\n");
|
|
2082
|
+
const signer = crypto.createSign("RSA-SHA256");
|
|
2083
|
+
signer.update(signatureInput);
|
|
2084
|
+
const signature = signer.sign(formattedKey, "base64");
|
|
2085
|
+
const jwt = `${signatureInput}.${signature.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_")}`;
|
|
2086
|
+
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
2087
|
+
method: "POST",
|
|
2088
|
+
headers: {
|
|
2089
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2090
|
+
},
|
|
2091
|
+
body: new URLSearchParams({
|
|
2092
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
2093
|
+
assertion: jwt
|
|
2094
|
+
}).toString()
|
|
2095
|
+
});
|
|
2096
|
+
if (!tokenRes.ok) {
|
|
2097
|
+
const errText = await tokenRes.text();
|
|
2098
|
+
throw new Error(`Failed to obtain Google access token: ${errText}`);
|
|
2099
|
+
}
|
|
2100
|
+
const tokenData = await tokenRes.json();
|
|
2101
|
+
if (!tokenData.access_token) {
|
|
2102
|
+
throw new Error("Google OAuth token response missing access_token");
|
|
2103
|
+
}
|
|
2104
|
+
return tokenData.access_token;
|
|
2105
|
+
}
|
|
2106
|
+
__name(getGoogleAccessToken, "getGoogleAccessToken");
|
|
2107
|
+
async function sendFcmPushNotification(dataSource, entityMap, msg) {
|
|
2108
|
+
try {
|
|
2109
|
+
const pushSettings = await getSettingsGroup(dataSource, entityMap, "push");
|
|
2110
|
+
let projectId = pushSettings.firebase_project_id?.trim();
|
|
2111
|
+
let clientEmail = pushSettings.firebase_client_email?.trim();
|
|
2112
|
+
let privateKey = pushSettings.firebase_private_key?.trim();
|
|
2113
|
+
if (pushSettings.firebase_service_account_json?.trim()) {
|
|
2114
|
+
try {
|
|
2115
|
+
const parsed = JSON.parse(pushSettings.firebase_service_account_json.trim());
|
|
2116
|
+
if (parsed.project_id) projectId = parsed.project_id;
|
|
2117
|
+
if (parsed.client_email) clientEmail = parsed.client_email;
|
|
2118
|
+
if (parsed.private_key) privateKey = parsed.private_key;
|
|
2119
|
+
} catch {
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
if (!projectId || !clientEmail || !privateKey) {
|
|
2123
|
+
return {
|
|
2124
|
+
success: false,
|
|
2125
|
+
error: "Firebase Service Account JSON / credentials are not configured"
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
const accessToken = await getGoogleAccessToken(clientEmail, privateKey);
|
|
2129
|
+
const fcmEndpoint = `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`;
|
|
2130
|
+
const fcmPayload = {
|
|
2131
|
+
message: {
|
|
2132
|
+
token: msg.token,
|
|
2133
|
+
notification: {
|
|
2134
|
+
title: msg.title,
|
|
2135
|
+
body: msg.body
|
|
2136
|
+
},
|
|
2137
|
+
data: msg.data || {}
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
const response = await fetch(fcmEndpoint, {
|
|
2141
|
+
method: "POST",
|
|
2142
|
+
headers: {
|
|
2143
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
2144
|
+
"Content-Type": "application/json"
|
|
2145
|
+
},
|
|
2146
|
+
body: JSON.stringify(fcmPayload)
|
|
2147
|
+
});
|
|
2148
|
+
const resData = await response.json();
|
|
2149
|
+
if (!response.ok) {
|
|
2150
|
+
const errorMsg = resData.error?.message || "FCM request failed";
|
|
2151
|
+
console.error("[fcm-push] Error sending push notification", errorMsg);
|
|
2152
|
+
return {
|
|
2153
|
+
success: false,
|
|
2154
|
+
error: errorMsg
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
return {
|
|
2158
|
+
success: true,
|
|
2159
|
+
messageId: resData.name
|
|
2160
|
+
};
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown push error";
|
|
2163
|
+
console.error("[fcm-push] Exception in sendFcmPushNotification", errorMsg);
|
|
2164
|
+
return {
|
|
2165
|
+
success: false,
|
|
2166
|
+
error: errorMsg
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
__name(sendFcmPushNotification, "sendFcmPushNotification");
|
|
2064
2171
|
|
|
2065
2172
|
// src/lib/order-notification-dispatcher.ts
|
|
2066
2173
|
var initialized = false;
|
|
@@ -2128,59 +2235,69 @@ async function loadBoundTemplate(triggerKey, channel, audienceType = "customers"
|
|
|
2128
2235
|
console.warn("[order-notifications] order_notification_bindings or message_templates missing from entityMap");
|
|
2129
2236
|
return null;
|
|
2130
2237
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
triggerKey,
|
|
2135
|
-
channel,
|
|
2136
|
-
type: audienceType,
|
|
2137
|
-
enabled: true
|
|
2138
|
-
}
|
|
2139
|
-
});
|
|
2140
|
-
if (!binding && audienceType === "customers") {
|
|
2141
|
-
binding = await bindingRepo.findOne({
|
|
2238
|
+
try {
|
|
2239
|
+
const bindingRepo = dataSource.getRepository(entityMap.order_notification_bindings);
|
|
2240
|
+
let binding = await bindingRepo.findOne({
|
|
2142
2241
|
where: {
|
|
2143
2242
|
triggerKey,
|
|
2144
2243
|
channel,
|
|
2244
|
+
type: audienceType,
|
|
2145
2245
|
enabled: true
|
|
2146
2246
|
}
|
|
2147
|
-
});
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
}
|
|
2157
|
-
const templateId = binding.messageTemplateId;
|
|
2158
|
-
if (templateId == null) {
|
|
2159
|
-
console.warn("[order-notifications] binding exists but no template selected", {
|
|
2160
|
-
triggerKey,
|
|
2161
|
-
channel,
|
|
2162
|
-
audienceType
|
|
2163
|
-
});
|
|
2164
|
-
return null;
|
|
2165
|
-
}
|
|
2166
|
-
const template = await dataSource.getRepository(entityMap.message_templates).findOne({
|
|
2167
|
-
where: {
|
|
2168
|
-
id: templateId,
|
|
2169
|
-
channel,
|
|
2170
|
-
deleted: false,
|
|
2171
|
-
enabled: true
|
|
2247
|
+
}).catch(() => null);
|
|
2248
|
+
if (!binding && audienceType === "customers") {
|
|
2249
|
+
binding = await bindingRepo.findOne({
|
|
2250
|
+
where: {
|
|
2251
|
+
triggerKey,
|
|
2252
|
+
channel,
|
|
2253
|
+
enabled: true
|
|
2254
|
+
}
|
|
2255
|
+
}).catch(() => null);
|
|
2172
2256
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2257
|
+
if (!binding) {
|
|
2258
|
+
console.warn("[order-notifications] no order_notification_bindings row", {
|
|
2259
|
+
triggerKey,
|
|
2260
|
+
channel,
|
|
2261
|
+
audienceType
|
|
2262
|
+
});
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
const templateId = binding.messageTemplateId;
|
|
2266
|
+
if (templateId == null) {
|
|
2267
|
+
console.warn("[order-notifications] binding exists but no template selected", {
|
|
2268
|
+
triggerKey,
|
|
2269
|
+
channel,
|
|
2270
|
+
audienceType
|
|
2271
|
+
});
|
|
2272
|
+
return null;
|
|
2273
|
+
}
|
|
2274
|
+
const template = await dataSource.getRepository(entityMap.message_templates).findOne({
|
|
2275
|
+
where: {
|
|
2276
|
+
id: templateId,
|
|
2277
|
+
channel,
|
|
2278
|
+
deleted: false,
|
|
2279
|
+
enabled: true
|
|
2280
|
+
}
|
|
2281
|
+
}).catch(() => null);
|
|
2282
|
+
if (!template) {
|
|
2283
|
+
console.warn("[order-notifications] bound template not found or disabled", {
|
|
2284
|
+
triggerKey,
|
|
2285
|
+
channel,
|
|
2286
|
+
audienceType,
|
|
2287
|
+
templateId
|
|
2288
|
+
});
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
return template;
|
|
2292
|
+
} catch (err) {
|
|
2293
|
+
console.warn("[order-notifications] loadBoundTemplate query failed", {
|
|
2176
2294
|
triggerKey,
|
|
2177
2295
|
channel,
|
|
2178
2296
|
audienceType,
|
|
2179
|
-
|
|
2297
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2180
2298
|
});
|
|
2181
2299
|
return null;
|
|
2182
2300
|
}
|
|
2183
|
-
return template;
|
|
2184
2301
|
}
|
|
2185
2302
|
__name(loadBoundTemplate, "loadBoundTemplate");
|
|
2186
2303
|
function mergeTriggerVariables(payload, rich) {
|
|
@@ -2197,9 +2314,11 @@ function mergeTriggerVariables(payload, rich) {
|
|
|
2197
2314
|
refundAmount: String(payload.refundAmount)
|
|
2198
2315
|
} : {}
|
|
2199
2316
|
};
|
|
2317
|
+
const extraVars = payload.extraVariables ?? {};
|
|
2200
2318
|
return {
|
|
2201
2319
|
...basic,
|
|
2202
|
-
...rich ?? {}
|
|
2320
|
+
...rich ?? {},
|
|
2321
|
+
...extraVars
|
|
2203
2322
|
};
|
|
2204
2323
|
}
|
|
2205
2324
|
__name(mergeTriggerVariables, "mergeTriggerVariables");
|
|
@@ -2211,6 +2330,82 @@ async function isAudienceEmailEnabled(dataSource, entityMap, audience) {
|
|
|
2211
2330
|
return settings[key] !== "false" && settings[key] !== "0";
|
|
2212
2331
|
}
|
|
2213
2332
|
__name(isAudienceEmailEnabled, "isAudienceEmailEnabled");
|
|
2333
|
+
async function isPushNotificationsEnabled(dataSource, entityMap) {
|
|
2334
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2335
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2336
|
+
return settings.push_enabled !== "false" && settings.push_enabled !== "0";
|
|
2337
|
+
}
|
|
2338
|
+
__name(isPushNotificationsEnabled, "isPushNotificationsEnabled");
|
|
2339
|
+
async function isAudiencePushEnabled(dataSource, entityMap, audience) {
|
|
2340
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2341
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2342
|
+
if (settings.push_enabled === "false" || settings.push_enabled === "0") return false;
|
|
2343
|
+
const key = `push_${audience}_enabled`;
|
|
2344
|
+
return settings[key] !== "false" && settings[key] !== "0";
|
|
2345
|
+
}
|
|
2346
|
+
__name(isAudiencePushEnabled, "isAudiencePushEnabled");
|
|
2347
|
+
async function sendPushNotificationForTrigger(triggerKey, orderId, audienceType, targetId, variables, dataSource, entityMap) {
|
|
2348
|
+
let template = await loadBoundTemplate(triggerKey, "mobile", audienceType, dataSource, entityMap);
|
|
2349
|
+
if (!template) {
|
|
2350
|
+
template = await loadBoundTemplate(triggerKey, "push", audienceType, dataSource, entityMap);
|
|
2351
|
+
}
|
|
2352
|
+
if (!template || !template.body) return;
|
|
2353
|
+
const rawTitle = template.subject?.trim() || "Order Notification";
|
|
2354
|
+
const title = applyTemplateVars(rawTitle, variables);
|
|
2355
|
+
const body = applyTemplateVars(template.body, variables);
|
|
2356
|
+
if (!entityMap.user_device_tokens) return;
|
|
2357
|
+
const tokenRepo = dataSource.getRepository(entityMap.user_device_tokens);
|
|
2358
|
+
let targetTokens = [];
|
|
2359
|
+
try {
|
|
2360
|
+
if (audienceType === "customers") {
|
|
2361
|
+
targetTokens = await tokenRepo.find({
|
|
2362
|
+
where: {
|
|
2363
|
+
userId: targetId,
|
|
2364
|
+
isActive: true
|
|
2365
|
+
},
|
|
2366
|
+
select: [
|
|
2367
|
+
"token"
|
|
2368
|
+
]
|
|
2369
|
+
});
|
|
2370
|
+
} else if (audienceType === "vendors") {
|
|
2371
|
+
targetTokens = await tokenRepo.find({
|
|
2372
|
+
where: {
|
|
2373
|
+
vendorId: targetId,
|
|
2374
|
+
isActive: true
|
|
2375
|
+
},
|
|
2376
|
+
select: [
|
|
2377
|
+
"token"
|
|
2378
|
+
]
|
|
2379
|
+
});
|
|
2380
|
+
} else if (audienceType === "admin") {
|
|
2381
|
+
targetTokens = await tokenRepo.find({
|
|
2382
|
+
where: {
|
|
2383
|
+
isActive: true
|
|
2384
|
+
},
|
|
2385
|
+
select: [
|
|
2386
|
+
"token"
|
|
2387
|
+
]
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
console.error("[order-notifications] Error querying device tokens for push", err);
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
for (const t of targetTokens) {
|
|
2395
|
+
if (t.token?.trim()) {
|
|
2396
|
+
await sendFcmPushNotification(dataSource, entityMap, {
|
|
2397
|
+
token: t.token.trim(),
|
|
2398
|
+
title,
|
|
2399
|
+
body,
|
|
2400
|
+
data: {
|
|
2401
|
+
orderId: String(orderId),
|
|
2402
|
+
triggerKey
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
__name(sendPushNotificationForTrigger, "sendPushNotificationForTrigger");
|
|
2214
2409
|
async function getOrderVendorEmails(orderId, orderVendorId, dataSource, entityMap) {
|
|
2215
2410
|
const emails = /* @__PURE__ */ new Set();
|
|
2216
2411
|
if (!entityMap.vendors) return [];
|
|
@@ -2319,7 +2514,23 @@ async function getAdminRecipientEmails(dataSource, entityMap) {
|
|
|
2319
2514
|
}
|
|
2320
2515
|
} catch {
|
|
2321
2516
|
}
|
|
2322
|
-
|
|
2517
|
+
const vendorEmails = /* @__PURE__ */ new Set();
|
|
2518
|
+
try {
|
|
2519
|
+
const vRows = await dataSource.query(`SELECT DISTINCT LOWER(TRIM(email)) AS email FROM vendors WHERE COALESCE(deleted, false) = false AND email IS NOT NULL AND TRIM(email) != ''`).catch(() => []);
|
|
2520
|
+
for (const r of vRows) {
|
|
2521
|
+
if (r.email) vendorEmails.add(r.email.toLowerCase());
|
|
2522
|
+
}
|
|
2523
|
+
const vuRows = await dataSource.query(`SELECT DISTINCT LOWER(TRIM(u.email)) AS email
|
|
2524
|
+
FROM users u
|
|
2525
|
+
LEFT JOIN user_groups g ON g.id = u."groupId"
|
|
2526
|
+
WHERE COALESCE(u.deleted, false) = false
|
|
2527
|
+
AND (LOWER(g.name) LIKE '%vendor%' OR u."groupId" = 5)`).catch(() => []);
|
|
2528
|
+
for (const r of vuRows) {
|
|
2529
|
+
if (r.email) vendorEmails.add(r.email.toLowerCase());
|
|
2530
|
+
}
|
|
2531
|
+
} catch {
|
|
2532
|
+
}
|
|
2533
|
+
return Array.from(emails).filter((e) => !vendorEmails.has(e.toLowerCase()));
|
|
2323
2534
|
}
|
|
2324
2535
|
__name(getAdminRecipientEmails, "getAdminRecipientEmails");
|
|
2325
2536
|
function formatBodyForEmailHtml(body) {
|
|
@@ -2473,12 +2684,13 @@ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms)
|
|
|
2473
2684
|
return;
|
|
2474
2685
|
}
|
|
2475
2686
|
}
|
|
2476
|
-
const [emailChannelEnabled, whatsappChannelEnabled] = await Promise.all([
|
|
2687
|
+
const [emailChannelEnabled, whatsappChannelEnabled, pushChannelEnabled] = await Promise.all([
|
|
2477
2688
|
isEmailNotificationsEnabled(dataSource, entityMap),
|
|
2478
|
-
isWhatsappNotificationsEnabled(dataSource, entityMap)
|
|
2689
|
+
isWhatsappNotificationsEnabled(dataSource, entityMap),
|
|
2690
|
+
isPushNotificationsEnabled(dataSource, entityMap)
|
|
2479
2691
|
]);
|
|
2480
|
-
if (!emailChannelEnabled && !whatsappChannelEnabled) {
|
|
2481
|
-
console.warn("[order-notifications] skip:
|
|
2692
|
+
if (!emailChannelEnabled && !whatsappChannelEnabled && !pushChannelEnabled) {
|
|
2693
|
+
console.warn("[order-notifications] skip: Email, WhatsApp, and Mobile Push notifications are disabled");
|
|
2482
2694
|
return;
|
|
2483
2695
|
}
|
|
2484
2696
|
const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, dataSource, entityMap);
|
|
@@ -2517,6 +2729,23 @@ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms)
|
|
|
2517
2729
|
} else {
|
|
2518
2730
|
console.log("[order-notifications] skip email: Email notifications channel is disabled");
|
|
2519
2731
|
}
|
|
2732
|
+
if (pushChannelEnabled) {
|
|
2733
|
+
const [pushCustomerEnabled, pushVendorEnabled, pushAdminEnabled] = await Promise.all([
|
|
2734
|
+
isAudiencePushEnabled(dataSource, entityMap, "customer"),
|
|
2735
|
+
isAudiencePushEnabled(dataSource, entityMap, "vendor"),
|
|
2736
|
+
isAudiencePushEnabled(dataSource, entityMap, "admin")
|
|
2737
|
+
]);
|
|
2738
|
+
const targetCustomerId = context?.order?.customerContactId || context?.order?.contactId || context?.order?.userId || context?.order?.id;
|
|
2739
|
+
if (pushCustomerEnabled && targetCustomerId) {
|
|
2740
|
+
await sendPushNotificationForTrigger(triggerKey, payload.orderId, "customers", String(targetCustomerId), variables, dataSource, entityMap);
|
|
2741
|
+
}
|
|
2742
|
+
if (pushVendorEnabled && payload.vendorId) {
|
|
2743
|
+
await sendPushNotificationForTrigger(triggerKey, payload.orderId, "vendors", String(payload.vendorId), variables, dataSource, entityMap);
|
|
2744
|
+
}
|
|
2745
|
+
if (pushAdminEnabled) {
|
|
2746
|
+
await sendPushNotificationForTrigger(triggerKey, payload.orderId, "admin", "admin", variables, dataSource, entityMap);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2520
2749
|
if (whatsappChannelEnabled) {
|
|
2521
2750
|
if (contactPhone) {
|
|
2522
2751
|
await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
|
|
@@ -2535,6 +2764,8 @@ function initWhatsappTriggerDispatcher(deps) {
|
|
|
2535
2764
|
if (initialized) return;
|
|
2536
2765
|
initialized = true;
|
|
2537
2766
|
const { dataSource, entityMap, getCms } = deps;
|
|
2767
|
+
void dataSource.query(`ALTER TYPE "order_notification_bindings_channel_enum" ADD VALUE IF NOT EXISTS 'mobile'`).catch(() => {
|
|
2768
|
+
});
|
|
2538
2769
|
notificationTriggerEmitter.onAnyTrigger((triggerKey, payload) => {
|
|
2539
2770
|
void handleTrigger(triggerKey, payload, dataSource, entityMap, getCms).catch((err) => {
|
|
2540
2771
|
console.error("[order-notifications]", triggerKey, err);
|
|
@@ -2594,4 +2825,4 @@ async function resendOrderNotification(triggerKey, orderId, deps) {
|
|
|
2594
2825
|
}
|
|
2595
2826
|
__name(resendOrderNotification, "resendOrderNotification");
|
|
2596
2827
|
|
|
2597
|
-
export { CHAT_EMAIL_LOG, EmailService, WhatsAppService, buildChatbotSystemPromptWithEmailTool, buildEventOrderTemplateVariables, buildTranscriptForLeadEmail, detectChatLeadIntent, emailPlugin, emailTemplates, initWhatsappTriggerDispatcher, isWhatsAppPluginEnabled, joinRecipientsForSend, mergeWhatsAppConfigLayers, parseEmailRecipientsFromConfig, parseIntentRecipientEmails, queueWhatsApp, registerWhatsAppQueueProcessor, renderEmail, renderLayout, resendOrderNotification, resolveChatEmailToolSettings, sendChatLeadEmail, serializeEmailRecipients, whatsAppConfigured };
|
|
2828
|
+
export { CHAT_EMAIL_LOG, EmailService, WhatsAppService, buildChatbotSystemPromptWithEmailTool, buildEventOrderTemplateVariables, buildTranscriptForLeadEmail, detectChatLeadIntent, emailPlugin, emailTemplates, getSettingsGroup, initWhatsappTriggerDispatcher, isWhatsAppPluginEnabled, joinRecipientsForSend, mergeWhatsAppConfigLayers, parseEmailRecipientsFromConfig, parseIntentRecipientEmails, queueWhatsApp, registerWhatsAppQueueProcessor, renderEmail, renderLayout, resendOrderNotification, resolveChatEmailToolSettings, sendChatLeadEmail, sendFcmPushNotification, serializeEmailRecipients, whatsAppConfigured };
|