@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.
Files changed (40) hide show
  1. package/README.md +30 -16
  2. package/dist/admin.cjs +2012 -437
  3. package/dist/admin.d.cts +13 -1
  4. package/dist/admin.d.ts +13 -1
  5. package/dist/admin.js +2013 -438
  6. package/dist/api.cjs +37 -37
  7. package/dist/api.js +5 -5
  8. package/dist/auth.cjs +52 -23
  9. package/dist/auth.d.cts +101 -2
  10. package/dist/auth.d.ts +101 -2
  11. package/dist/auth.js +3 -2
  12. package/dist/chunk-3EOM4V2M.cjs +452 -0
  13. package/dist/{chunk-2HU5R2JE.js → chunk-AYERBA7I.js} +1 -7
  14. package/dist/{chunk-YXH2UUEZ.js → chunk-CTXBYO2J.js} +290 -59
  15. package/dist/{chunk-LMJ7RKPF.cjs → chunk-JN4AFHZ4.cjs} +311 -59
  16. package/dist/{chunk-JE22VP6S.js → chunk-MXIWUFBP.js} +1 -1
  17. package/dist/{chunk-W42UZLQO.js → chunk-NUXR7BUG.js} +2819 -2597
  18. package/dist/chunk-RK5ETF2I.js +434 -0
  19. package/dist/{chunk-4PMK3RNA.cjs → chunk-TJ2MIQUT.cjs} +1 -7
  20. package/dist/{chunk-KOTXDSQB.cjs → chunk-UQWZUZ5X.cjs} +1 -1
  21. package/dist/{chunk-YC4NUZCS.js → chunk-WRKV6MHB.js} +414 -2
  22. package/dist/{chunk-XUCKZPML.cjs → chunk-YOW457RA.cjs} +2872 -2657
  23. package/dist/{chunk-Q3HQEM4R.cjs → chunk-YV5PK4JW.cjs} +421 -1
  24. package/dist/cli.cjs +13 -22
  25. package/dist/cli.js +13 -22
  26. package/dist/{emit-order-notification-trigger-6XX7LSC4.js → emit-order-notification-trigger-HZQPPSFB.js} +1 -1
  27. package/dist/{emit-order-notification-trigger-NASG7ZEO.cjs → emit-order-notification-trigger-NR3VN6C6.cjs} +3 -3
  28. package/dist/{event-order-defaults-7Z3UZOEH.cjs → event-order-defaults-DU7V3YND.cjs} +9 -9
  29. package/dist/{event-order-defaults-XQ3IDPD7.js → event-order-defaults-H4RT7NMR.js} +1 -1
  30. package/dist/index.cjs +379 -309
  31. package/dist/index.d.cts +136 -6
  32. package/dist/index.d.ts +136 -6
  33. package/dist/index.js +58 -24
  34. package/dist/migrations/1782400000000-CreateUserDeviceTokens.ts +36 -0
  35. package/dist/migrations/1782500000000-AddPushToOrderNotificationBindingsChannelEnum.ts +15 -0
  36. package/dist/{order-notification-dispatcher-6WNG24NY.js → order-notification-dispatcher-AT4IFAGL.js} +1 -1
  37. package/dist/{order-notification-dispatcher-6XSU3AR7.cjs → order-notification-dispatcher-YBAWDOIO.cjs} +7 -3
  38. package/package.json +1 -1
  39. package/dist/chunk-VUQFARRT.cjs +0 -182
  40. 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-XQ3IDPD7.js');
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
- const bindingRepo = dataSource.getRepository(entityMap.order_notification_bindings);
2132
- let binding = await bindingRepo.findOne({
2133
- where: {
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
- if (!binding) {
2150
- console.warn("[order-notifications] no order_notification_bindings row", {
2151
- triggerKey,
2152
- channel,
2153
- audienceType
2154
- });
2155
- return null;
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
- if (!template) {
2175
- console.warn("[order-notifications] bound template not found or disabled", {
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
- templateId
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
- return Array.from(emails);
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: Both Email and WhatsApp notifications are disabled");
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 };