@consilioweb/payload-support 2.0.1 → 4.0.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/README.md +670 -402
- package/dist/components/RichTextEditor/index.d.ts +19 -0
- package/dist/components/TicketConversation/RewriteDropdown.d.ts +6 -0
- package/dist/components/TicketConversation/SkeletonText.d.ts +4 -0
- package/dist/components/TicketConversation/components/AISummaryPanel.d.ts +14 -0
- package/dist/components/TicketConversation/components/ActionPanels.d.ts +47 -0
- package/dist/components/TicketConversation/components/ActivityLog.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientBar.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientHistory.d.ts +24 -0
- package/dist/components/TicketConversation/components/CodeBlock.d.ts +26 -0
- package/dist/components/TicketConversation/components/CodeBlockInserter.d.ts +12 -0
- package/dist/components/TicketConversation/components/QuickActions.d.ts +32 -0
- package/dist/components/TicketConversation/components/TicketHeader.d.ts +19 -0
- package/dist/components/TicketConversation/components/TimeTrackingPanel.d.ts +24 -0
- package/dist/components/TicketConversation/config.d.ts +10 -0
- package/dist/components/TicketConversation/config.js +1 -40
- package/dist/components/TicketConversation/constants.d.ts +56 -0
- package/dist/components/TicketConversation/context.d.ts +53 -0
- package/dist/components/TicketConversation/hooks/useAI.d.ts +21 -0
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.d.ts +3 -0
- package/dist/components/TicketConversation/hooks/useFeatures.d.ts +23 -0
- package/dist/components/TicketConversation/hooks/useFeatures.js +61 -0
- package/dist/components/TicketConversation/hooks/useMessageActions.d.ts +20 -0
- package/dist/components/TicketConversation/hooks/useReply.d.ts +27 -0
- package/dist/components/TicketConversation/hooks/useTicketActions.d.ts +58 -0
- package/dist/components/TicketConversation/hooks/useTimeTracking.d.ts +18 -0
- package/dist/components/TicketConversation/hooks/useTranslation.d.ts +33 -0
- package/dist/components/TicketConversation/index.d.ts +4 -0
- package/dist/components/TicketConversation/index.js +2 -2
- package/dist/components/TicketConversation/types.d.ts +56 -0
- package/dist/components/TicketConversation/utils.d.ts +2 -0
- package/dist/index.cjs +940 -404
- package/dist/index.d.cts +119 -7
- package/dist/index.d.ts +119 -7
- package/dist/index.js +936 -405
- package/dist/utils/features.d.ts +123 -0
- package/dist/utils/features.js +147 -0
- package/dist/views/BillingView/client.d.ts +2 -0
- package/dist/views/BillingView/index.d.ts +4 -0
- package/dist/views/ChatView/client.d.ts +2 -0
- package/dist/views/ChatView/client.js +9 -9
- package/dist/views/ChatView/index.d.ts +4 -0
- package/dist/views/CrmView/client.d.ts +2 -0
- package/dist/views/CrmView/client.js +6 -9
- package/dist/views/CrmView/index.d.ts +4 -0
- package/dist/views/EmailTrackingView/client.d.ts +2 -0
- package/dist/views/EmailTrackingView/index.d.ts +4 -0
- package/dist/views/ImportConversationView/client.d.ts +2 -0
- package/dist/views/ImportConversationView/index.d.ts +4 -0
- package/dist/views/LogsView/client.d.ts +2 -0
- package/dist/views/LogsView/index.d.ts +4 -0
- package/dist/views/NewTicketView/client.d.ts +2 -0
- package/dist/views/NewTicketView/index.d.ts +4 -0
- package/dist/views/PendingEmailsView/client.d.ts +2 -0
- package/dist/views/PendingEmailsView/index.d.ts +4 -0
- package/dist/views/SupportDashboardView/client.d.ts +2 -0
- package/dist/views/SupportDashboardView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/NextActionItem.d.ts +6 -0
- package/dist/views/TicketDetailView/RewriteDropdown.d.ts +7 -0
- package/dist/views/TicketDetailView/client.d.ts +2 -0
- package/dist/views/TicketDetailView/client.js +2 -2
- package/dist/views/TicketDetailView/constants.d.ts +13 -0
- package/dist/views/TicketDetailView/helpers.d.ts +2 -0
- package/dist/views/TicketDetailView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/types.d.ts +40 -0
- package/dist/views/TicketInboxView/client.d.ts +2 -0
- package/dist/views/TicketInboxView/client.js +5 -5
- package/dist/views/TicketInboxView/index.d.ts +4 -0
- package/dist/views/TicketingSettingsView/client.d.ts +2 -0
- package/dist/views/TicketingSettingsView/client.js +5 -4
- package/dist/views/TicketingSettingsView/index.d.ts +4 -0
- package/dist/views/TimeDashboardView/client.d.ts +2 -0
- package/dist/views/TimeDashboardView/index.d.ts +4 -0
- package/dist/views/shared/AdminViewHeader.d.ts +13 -0
- package/dist/views/shared/ErrorBoundary.d.ts +17 -0
- package/dist/views/shared/Skeleton.d.ts +21 -0
- package/dist/views/shared/StatusPill.d.ts +8 -0
- package/dist/views/shared/adminTokens.d.ts +19 -0
- package/dist/views/shared/config.d.ts +8 -0
- package/dist/views/shared/config.js +1 -40
- package/dist/views/shared/dateLocale.d.ts +5 -0
- package/dist/views/shared/index.d.ts +12 -0
- package/dist/views/shared/index.js +2 -1
- package/dist/views/shared/sla.d.ts +14 -0
- package/dist/views.d.ts +13 -13
- package/package.json +16 -31
- package/src/collections/ChatMessages.ts +59 -2
- package/src/collections/ClientSummaries.ts +10 -4
- package/src/collections/SupportClients.ts +29 -0
- package/src/collections/TicketCollaborators.ts +6 -1
- package/src/collections/TicketMessages.ts +49 -24
- package/src/collections/Tickets.ts +117 -79
- package/src/collections/TimeEntries.ts +57 -32
- package/src/collections/WebhookEndpoints.ts +44 -2
- package/src/components/TicketConversation/config.ts +16 -80
- package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
- package/src/components/TicketConversation/index.tsx +2 -2
- package/src/endpoints/admin-chat.ts +3 -3
- package/src/endpoints/ai-agent.ts +3 -3
- package/src/endpoints/ai.ts +5 -4
- package/src/endpoints/auth-2fa.ts +16 -4
- package/src/endpoints/capabilities.ts +6 -6
- package/src/endpoints/chat.ts +7 -5
- package/src/endpoints/chatbot.ts +48 -2
- package/src/endpoints/client-intelligence.ts +6 -5
- package/src/endpoints/email-stats.ts +19 -3
- package/src/endpoints/import-conversation.ts +1 -1
- package/src/endpoints/index.ts +1 -1
- package/src/endpoints/invite-collaborator.ts +29 -3
- package/src/endpoints/login.ts +15 -2
- package/src/endpoints/oauth-google.ts +130 -8
- package/src/endpoints/process-scheduled.ts +13 -8
- package/src/endpoints/resend-notification.ts +3 -3
- package/src/endpoints/round-robin-config.ts +30 -20
- package/src/endpoints/send-reminder.ts +3 -3
- package/src/endpoints/settings.ts +28 -77
- package/src/endpoints/signature.ts +9 -2
- package/src/endpoints/ticket-synthesis.ts +3 -3
- package/src/endpoints/transfer-ticket.ts +28 -3
- package/src/endpoints/typing.ts +117 -14
- package/src/endpoints/user-prefs.ts +5 -2
- package/src/index.ts +6 -2
- package/src/plugin.ts +12 -0
- package/src/portal/LiveChat.tsx +33 -22
- package/src/portal/auth/layout.tsx +19 -1
- package/src/portal/auth/tickets/detail/MessageBody.tsx +88 -0
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
- package/src/portal/auth/tickets/detail/page.tsx +2 -6
- package/src/portal/login/page.tsx +11 -3
- package/src/types/lucide-react.d.ts +10 -2
- package/src/utils/aiAgent.ts +2 -1
- package/src/utils/aiProvider.ts +21 -0
- package/src/utils/features.ts +297 -0
- package/src/utils/fireWebhooks.ts +10 -1
- package/src/utils/generateTicketSynthesis.ts +2 -1
- package/src/utils/index.ts +4 -2
- package/src/utils/rateLimiter.ts +39 -5
- package/src/utils/readSettings.ts +199 -16
- package/src/utils/ticketAccess.ts +16 -1
- package/src/utils/twoFactorChallenge.ts +80 -0
- package/src/utils/urlSafety.ts +230 -0
- package/src/utils/webhookDispatcher.ts +24 -8
- package/src/views/ChatView/client.tsx +13 -9
- package/src/views/CrmView/client.tsx +10 -11
- package/src/views/TicketDetailView/client.tsx +2 -2
- package/src/views/TicketInboxView/client.tsx +10 -5
- package/src/views/TicketingSettingsView/client.tsx +8 -6
- package/src/views/shared/config.ts +14 -80
- package/src/views/shared/index.ts +3 -1
- package/dist/components/RichTextEditor/index.cjs +0 -279
- package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
- package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
- package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
- package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
- package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
- package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
- package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
- package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
- package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
- package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
- package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
- package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
- package/dist/components/TicketConversation/config.cjs +0 -44
- package/dist/components/TicketConversation/constants.cjs +0 -127
- package/dist/components/TicketConversation/context.cjs +0 -13
- package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
- package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
- package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
- package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
- package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
- package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -101
- package/dist/components/TicketConversation/index.cjs +0 -1114
- package/dist/components/TicketConversation/types.cjs +0 -2
- package/dist/components/TicketConversation/utils.cjs +0 -27
- package/dist/views/BillingView/client.cjs +0 -469
- package/dist/views/BillingView/index.cjs +0 -34
- package/dist/views/ChatView/client.cjs +0 -301
- package/dist/views/ChatView/index.cjs +0 -34
- package/dist/views/CrmView/client.cjs +0 -458
- package/dist/views/CrmView/index.cjs +0 -34
- package/dist/views/EmailTrackingView/client.cjs +0 -171
- package/dist/views/EmailTrackingView/index.cjs +0 -34
- package/dist/views/ImportConversationView/client.cjs +0 -238
- package/dist/views/ImportConversationView/index.cjs +0 -34
- package/dist/views/LogsView/client.cjs +0 -148
- package/dist/views/LogsView/index.cjs +0 -32
- package/dist/views/NewTicketView/client.cjs +0 -213
- package/dist/views/NewTicketView/index.cjs +0 -32
- package/dist/views/PendingEmailsView/client.cjs +0 -471
- package/dist/views/PendingEmailsView/index.cjs +0 -34
- package/dist/views/SupportDashboardView/client.cjs +0 -437
- package/dist/views/SupportDashboardView/index.cjs +0 -34
- package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
- package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
- package/dist/views/TicketDetailView/client.cjs +0 -1412
- package/dist/views/TicketDetailView/constants.cjs +0 -20
- package/dist/views/TicketDetailView/helpers.cjs +0 -23
- package/dist/views/TicketDetailView/index.cjs +0 -34
- package/dist/views/TicketDetailView/types.cjs +0 -2
- package/dist/views/TicketInboxView/client.cjs +0 -359
- package/dist/views/TicketInboxView/index.cjs +0 -32
- package/dist/views/TicketingSettingsView/client.cjs +0 -740
- package/dist/views/TicketingSettingsView/index.cjs +0 -34
- package/dist/views/TimeDashboardView/client.cjs +0 -180
- package/dist/views/TimeDashboardView/index.cjs +0 -34
- package/dist/views/shared/AdminViewHeader.cjs +0 -68
- package/dist/views/shared/ErrorBoundary.cjs +0 -55
- package/dist/views/shared/Skeleton.cjs +0 -77
- package/dist/views/shared/StatusPill.cjs +0 -47
- package/dist/views/shared/adminTokens.cjs +0 -34
- package/dist/views/shared/config.cjs +0 -44
- package/dist/views/shared/dateLocale.cjs +0 -5
- package/dist/views/shared/index.cjs +0 -72
- package/dist/views/shared/sla.cjs +0 -37
- package/dist/views.cjs +0 -86
- package/dist/views.d.cts +0 -13
- package/src/__tests__/aiSummaryRendering.test.ts +0 -21
- package/src/__tests__/authResponses.test.ts +0 -69
- package/src/__tests__/capabilities.test.ts +0 -71
- package/src/__tests__/generateTrackingToken.test.ts +0 -67
- package/src/__tests__/integration/ai-agent.test.ts +0 -46
- package/src/__tests__/integration/auth.test.ts +0 -43
- package/src/__tests__/integration/automation-rules.test.ts +0 -68
- package/src/__tests__/integration/buildTestPayload.ts +0 -52
- package/src/__tests__/integration/channels.test.ts +0 -52
- package/src/__tests__/integration/core-behaviors.test.ts +0 -81
- package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
- package/src/__tests__/integration/digest.test.ts +0 -48
- package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
- package/src/__tests__/integration/isolation.test.ts +0 -81
- package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
- package/src/__tests__/integration/nps.test.ts +0 -37
- package/src/__tests__/integration/per-team-sla.test.ts +0 -44
- package/src/__tests__/integration/push.test.ts +0 -75
- package/src/__tests__/integration/sanitization.test.ts +0 -35
- package/src/__tests__/integration/sla-pause.test.ts +0 -39
- package/src/__tests__/integration/smoke.test.ts +0 -26
- package/src/__tests__/integration/snooze.test.ts +0 -68
- package/src/__tests__/integration/teams.test.ts +0 -38
- package/src/__tests__/rateLimiter.test.ts +0 -83
- package/src/__tests__/sanitizeHtml.test.ts +0 -165
- package/src/__tests__/sla.test.ts +0 -258
- package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
- package/src/__tests__/translations.test.ts +0 -67
- package/src/__tests__/webhookSecurity.test.ts +0 -58
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface SkeletonProps {
|
|
3
|
+
width?: string | number;
|
|
4
|
+
height?: string | number;
|
|
5
|
+
borderRadius?: string;
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
}
|
|
8
|
+
export declare function Skeleton({ width, height, borderRadius, style }: SkeletonProps): React.JSX.Element;
|
|
9
|
+
export declare function SkeletonText({ lines, width }: {
|
|
10
|
+
lines?: number;
|
|
11
|
+
width?: string | number;
|
|
12
|
+
}): React.JSX.Element;
|
|
13
|
+
export declare function SkeletonCard({ height }: {
|
|
14
|
+
height?: string;
|
|
15
|
+
}): React.JSX.Element;
|
|
16
|
+
export declare function SkeletonTable({ rows, columns }: {
|
|
17
|
+
rows?: number;
|
|
18
|
+
columns?: number;
|
|
19
|
+
}): React.JSX.Element;
|
|
20
|
+
export declare function SkeletonDashboard(): React.JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type TicketStatusVariant = 'open' | 'pending' | 'waiting_client' | 'resolved' | 'closed' | 'escalated' | 'sla-breach';
|
|
3
|
+
export interface StatusPillProps {
|
|
4
|
+
status: TicketStatusVariant | string;
|
|
5
|
+
label?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const StatusPill: React.FC<StatusPillProps>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export declare const V: {
|
|
3
|
+
readonly text: "var(--theme-text)";
|
|
4
|
+
readonly textSecondary: "var(--theme-elevation-500)";
|
|
5
|
+
readonly bg: "var(--theme-elevation-50)";
|
|
6
|
+
readonly bgCard: "var(--theme-elevation-100)";
|
|
7
|
+
readonly border: "var(--theme-elevation-300)";
|
|
8
|
+
readonly blue: "#2563eb";
|
|
9
|
+
readonly amber: "#d97706";
|
|
10
|
+
readonly orange: "#ea580c";
|
|
11
|
+
readonly green: "#16a34a";
|
|
12
|
+
readonly red: "#dc2626";
|
|
13
|
+
readonly cyan: "#2563eb";
|
|
14
|
+
readonly yellow: "#d97706";
|
|
15
|
+
};
|
|
16
|
+
export declare const btnStyle: (bg: string, opts?: {
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
small?: boolean;
|
|
19
|
+
}) => React.CSSProperties;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticketing feature flags — re-export of the shared implementation.
|
|
3
|
+
*
|
|
4
|
+
* See `utils/features.ts`. This module is kept so that
|
|
5
|
+
* `views/shared/index.ts` and existing view imports keep resolving.
|
|
6
|
+
*/
|
|
7
|
+
export { DEFAULT_TICKETING_FEATURES, DEFAULT_TICKETING_FEATURES as DEFAULT_FEATURES, FEATURES_CACHE_KEY, fetchFeatures, getFeatures, normalizeFeatures, saveFeatures, } from '../../utils/features.js';
|
|
8
|
+
export type { TicketingFeatures } from '../../utils/features.js';
|
|
@@ -1,40 +1 @@
|
|
|
1
|
-
|
|
2
|
-
timeTracking: true,
|
|
3
|
-
ai: true,
|
|
4
|
-
satisfaction: true,
|
|
5
|
-
chat: true,
|
|
6
|
-
emailTracking: true,
|
|
7
|
-
canned: true,
|
|
8
|
-
merge: true,
|
|
9
|
-
snooze: true,
|
|
10
|
-
externalMessages: true,
|
|
11
|
-
clientHistory: true,
|
|
12
|
-
activityLog: true,
|
|
13
|
-
splitTicket: true,
|
|
14
|
-
scheduledReplies: true,
|
|
15
|
-
autoClose: true,
|
|
16
|
-
autoCloseDays: 7,
|
|
17
|
-
roundRobin: false
|
|
18
|
-
};
|
|
19
|
-
const STORAGE_KEY = "ticketing_features";
|
|
20
|
-
function getFeatures() {
|
|
21
|
-
if (typeof window === "undefined") return DEFAULT_FEATURES;
|
|
22
|
-
try {
|
|
23
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
24
|
-
if (stored) {
|
|
25
|
-
const parsed = JSON.parse(stored);
|
|
26
|
-
return { ...DEFAULT_FEATURES, ...parsed };
|
|
27
|
-
}
|
|
28
|
-
} catch {
|
|
29
|
-
}
|
|
30
|
-
return DEFAULT_FEATURES;
|
|
31
|
-
}
|
|
32
|
-
function saveFeatures(features) {
|
|
33
|
-
if (typeof window === "undefined") return;
|
|
34
|
-
try {
|
|
35
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(features));
|
|
36
|
-
} catch {
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { DEFAULT_FEATURES, getFeatures, saveFeatures };
|
|
1
|
+
export { DEFAULT_TICKETING_FEATURES as DEFAULT_FEATURES, DEFAULT_TICKETING_FEATURES, FEATURES_CACHE_KEY, fetchFeatures, getFeatures, normalizeFeatures, saveFeatures } from '../../utils/features.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { AdminErrorBoundary } from './ErrorBoundary.js';
|
|
2
|
+
export { V, btnStyle } from './adminTokens.js';
|
|
3
|
+
export { AdminViewHeader } from './AdminViewHeader.js';
|
|
4
|
+
export { Skeleton, SkeletonText, SkeletonCard, SkeletonTable, SkeletonDashboard } from './Skeleton.js';
|
|
5
|
+
export { getFeatures, saveFeatures, fetchFeatures, normalizeFeatures, DEFAULT_FEATURES } from './config.js';
|
|
6
|
+
export type { TicketingFeatures } from './config.js';
|
|
7
|
+
export { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
|
|
8
|
+
export type { UseFeaturesResult } from '../../components/TicketConversation/hooks/useFeatures.js';
|
|
9
|
+
export { StatusPill } from './StatusPill.js';
|
|
10
|
+
export type { StatusPillProps, TicketStatusVariant } from './StatusPill.js';
|
|
11
|
+
export { computeSlaState, formatSlaRemaining } from './sla.js';
|
|
12
|
+
export type { SlaState } from './sla.js';
|
|
@@ -2,6 +2,7 @@ export { AdminErrorBoundary } from './ErrorBoundary.js';
|
|
|
2
2
|
export { V, btnStyle } from './adminTokens.js';
|
|
3
3
|
export { AdminViewHeader } from './AdminViewHeader.js';
|
|
4
4
|
export { Skeleton, SkeletonCard, SkeletonDashboard, SkeletonTable, SkeletonText } from './Skeleton.js';
|
|
5
|
-
export { DEFAULT_FEATURES, getFeatures, saveFeatures } from './config.js';
|
|
5
|
+
export { DEFAULT_FEATURES, fetchFeatures, getFeatures, normalizeFeatures, saveFeatures } from './config.js';
|
|
6
|
+
export { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
|
|
6
7
|
export { StatusPill } from './StatusPill.js';
|
|
7
8
|
export { computeSlaState, formatSlaRemaining } from './sla.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SlaState = 'ok' | 'warn' | 'breach' | 'none' | 'met';
|
|
2
|
+
export interface SlaInput {
|
|
3
|
+
slaFirstResponseDue?: string | null;
|
|
4
|
+
slaFirstResponseBreached?: boolean | null;
|
|
5
|
+
firstResponseAt?: string | null;
|
|
6
|
+
slaResolutionDue?: string | null;
|
|
7
|
+
slaResolutionBreached?: boolean | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function computeSlaState(t: SlaInput, now?: Date): {
|
|
10
|
+
state: SlaState;
|
|
11
|
+
remainingMs: number | null;
|
|
12
|
+
due: string | null;
|
|
13
|
+
};
|
|
14
|
+
export declare function formatSlaRemaining(remainingMs: number | null): string;
|
package/dist/views.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export { default as TicketInboxView } from './views/TicketInboxView/index';
|
|
2
|
-
export { default as TicketDetailView } from './views/TicketDetailView/index';
|
|
3
|
-
export { default as SupportDashboardView } from './views/SupportDashboardView/index';
|
|
4
|
-
export { default as NewTicketView } from './views/NewTicketView/index';
|
|
5
|
-
export { default as TicketingSettingsView } from './views/TicketingSettingsView/index';
|
|
6
|
-
export { default as LogsView } from './views/LogsView/index';
|
|
7
|
-
export { default as ChatView } from './views/ChatView/index';
|
|
8
|
-
export { default as CrmView } from './views/CrmView/index';
|
|
9
|
-
export { default as PendingEmailsView } from './views/PendingEmailsView/index';
|
|
10
|
-
export { default as EmailTrackingView } from './views/EmailTrackingView/index';
|
|
11
|
-
export { default as BillingView } from './views/BillingView/index';
|
|
12
|
-
export { default as TimeDashboardView } from './views/TimeDashboardView/index';
|
|
13
|
-
export { default as ImportConversationView } from './views/ImportConversationView/index';
|
|
1
|
+
export { default as TicketInboxView } from './views/TicketInboxView/index.js';
|
|
2
|
+
export { default as TicketDetailView } from './views/TicketDetailView/index.js';
|
|
3
|
+
export { default as SupportDashboardView } from './views/SupportDashboardView/index.js';
|
|
4
|
+
export { default as NewTicketView } from './views/NewTicketView/index.js';
|
|
5
|
+
export { default as TicketingSettingsView } from './views/TicketingSettingsView/index.js';
|
|
6
|
+
export { default as LogsView } from './views/LogsView/index.js';
|
|
7
|
+
export { default as ChatView } from './views/ChatView/index.js';
|
|
8
|
+
export { default as CrmView } from './views/CrmView/index.js';
|
|
9
|
+
export { default as PendingEmailsView } from './views/PendingEmailsView/index.js';
|
|
10
|
+
export { default as EmailTrackingView } from './views/EmailTrackingView/index.js';
|
|
11
|
+
export { default as BillingView } from './views/BillingView/index.js';
|
|
12
|
+
export { default as TimeDashboardView } from './views/TimeDashboardView/index.js';
|
|
13
|
+
export { default as ImportConversationView } from './views/ImportConversationView/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@consilioweb/payload-support",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -31,14 +31,11 @@
|
|
|
31
31
|
"import": {
|
|
32
32
|
"types": "./dist/views.d.ts",
|
|
33
33
|
"default": "./dist/views.js"
|
|
34
|
-
},
|
|
35
|
-
"require": {
|
|
36
|
-
"types": "./dist/views.d.cts",
|
|
37
|
-
"default": "./dist/views.cjs"
|
|
38
34
|
}
|
|
39
35
|
},
|
|
40
36
|
"./components/TicketConversation": {
|
|
41
37
|
"import": {
|
|
38
|
+
"types": "./dist/components/TicketConversation/index.d.ts",
|
|
42
39
|
"default": "./dist/components/TicketConversation/index.js"
|
|
43
40
|
}
|
|
44
41
|
}
|
|
@@ -46,6 +43,9 @@
|
|
|
46
43
|
"files": [
|
|
47
44
|
"dist",
|
|
48
45
|
"src",
|
|
46
|
+
"!src/__tests__",
|
|
47
|
+
"!src/**/*.test.ts",
|
|
48
|
+
"!src/**/*.test.tsx",
|
|
49
49
|
"README.md",
|
|
50
50
|
"LICENSE"
|
|
51
51
|
],
|
|
@@ -85,34 +85,19 @@
|
|
|
85
85
|
"author": "ConsilioWEB <contact@consilioweb.fr> (https://consilioweb.fr)",
|
|
86
86
|
"license": "MIT",
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@payloadcms/next": "^3.
|
|
89
|
-
"@payloadcms/translations": "^3.0.0",
|
|
90
|
-
"@payloadcms/ui": "^3.0.0",
|
|
88
|
+
"@payloadcms/next": "^3.37.0",
|
|
91
89
|
"lucide-react": ">=0.300.0",
|
|
92
|
-
"next": "^
|
|
93
|
-
"payload": "^3.
|
|
94
|
-
"react": "^
|
|
95
|
-
"react-dom": "^
|
|
96
|
-
},
|
|
97
|
-
"peerDependenciesMeta": {
|
|
98
|
-
"@payloadcms/ui": {
|
|
99
|
-
"optional": true
|
|
100
|
-
},
|
|
101
|
-
"@payloadcms/translations": {
|
|
102
|
-
"optional": true
|
|
103
|
-
},
|
|
104
|
-
"@payloadcms/next": {
|
|
105
|
-
"optional": true
|
|
106
|
-
},
|
|
107
|
-
"lucide-react": {
|
|
108
|
-
"optional": true
|
|
109
|
-
},
|
|
110
|
-
"next": {
|
|
111
|
-
"optional": true
|
|
112
|
-
}
|
|
90
|
+
"next": "^15.2.9 || ^16.0.0",
|
|
91
|
+
"payload": "^3.37.0",
|
|
92
|
+
"react": "^19.0.0",
|
|
93
|
+
"react-dom": "^19.0.0"
|
|
113
94
|
},
|
|
114
95
|
"engines": {
|
|
115
|
-
"node": ">=
|
|
96
|
+
"node": ">=20.9.0"
|
|
97
|
+
},
|
|
98
|
+
"publishConfig": {
|
|
99
|
+
"access": "public",
|
|
100
|
+
"provenance": true
|
|
116
101
|
},
|
|
117
102
|
"devDependencies": {
|
|
118
103
|
"@payloadcms/db-sqlite": "^3.86.0",
|
|
@@ -142,7 +127,7 @@
|
|
|
142
127
|
"web-push": "^3.6.7"
|
|
143
128
|
},
|
|
144
129
|
"scripts": {
|
|
145
|
-
"build": "tsup",
|
|
130
|
+
"build": "tsup && tsc -p tsconfig.types.json && node scripts/copy-subpath-types.mjs",
|
|
146
131
|
"typecheck": "tsc --noEmit",
|
|
147
132
|
"test": "vitest run",
|
|
148
133
|
"test:watch": "vitest",
|
|
@@ -1,5 +1,56 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { APIError } from 'payload'
|
|
2
|
+
import type { CollectionBeforeChangeHook, CollectionConfig } from 'payload'
|
|
2
3
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
4
|
+
import { dbFind } from '../utils/db'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cross-tenant write guard for support-clients.
|
|
8
|
+
*
|
|
9
|
+
* `access.create` used to be `!!req.user`, which any principal of ANY auth
|
|
10
|
+
* collection satisfies — including a front-office member of the host app. With
|
|
11
|
+
* no hook validating the row, an attacker could POST a message attributed to
|
|
12
|
+
* someone else's `client` and flagged `senderType: 'agent'` with an arbitrary
|
|
13
|
+
* `agent` relation: the agent console and the targeted client both render it as
|
|
14
|
+
* a genuine conversation (phishing over a trusted channel).
|
|
15
|
+
*
|
|
16
|
+
* Mirrors `createRestrictClientTicketTarget` on ticket-messages: the client's
|
|
17
|
+
* own identity is FORCED onto the row instead of being trusted from the body.
|
|
18
|
+
* Internal writers (endpoints/chat.ts, endpoints/admin-chat.ts,
|
|
19
|
+
* collections/TicketMessages.ts) all write with `overrideAccess: true` and carry
|
|
20
|
+
* no support-client user, so they are untouched.
|
|
21
|
+
*/
|
|
22
|
+
function createRestrictClientChatWrite(slugs: CollectionSlugs): CollectionBeforeChangeHook {
|
|
23
|
+
return async ({ data, req }) => {
|
|
24
|
+
if (req.user?.collection !== slugs.supportClients) return data
|
|
25
|
+
data.client = req.user.id
|
|
26
|
+
data.senderType = 'client'
|
|
27
|
+
delete data.agent
|
|
28
|
+
|
|
29
|
+
// …and the SESSION has to be theirs too. Forcing `client` alone still let a
|
|
30
|
+
// client drop a line INTO someone else's conversation: the agent console
|
|
31
|
+
// loads a thread by session id with `overrideAccess: true`
|
|
32
|
+
// (endpoints/admin-chat.ts), so the injected message surfaces in the target's
|
|
33
|
+
// thread even though `access.read` hides it from the target themselves.
|
|
34
|
+
// Session ids are 16 random bytes, so this is depth rather than a reachable
|
|
35
|
+
// hole — and it costs one indexed lookup on a path no internal writer takes
|
|
36
|
+
// (chat.ts / admin-chat.ts write without a support-client `req`).
|
|
37
|
+
const session = typeof data.session === 'string' ? data.session : null
|
|
38
|
+
if (!session) return data
|
|
39
|
+
|
|
40
|
+
const existing = await dbFind(req.payload, slugs.chatMessages, {
|
|
41
|
+
where: { session: { equals: session } },
|
|
42
|
+
limit: 1,
|
|
43
|
+
depth: 0,
|
|
44
|
+
overrideAccess: true,
|
|
45
|
+
})
|
|
46
|
+
const owner = existing.docs[0]?.client
|
|
47
|
+
const ownerId = owner && typeof owner === 'object' ? (owner as { id?: unknown }).id : owner
|
|
48
|
+
if (ownerId !== undefined && ownerId !== null && String(ownerId) !== String(req.user.id)) {
|
|
49
|
+
throw new APIError('Session inaccessible.', 403)
|
|
50
|
+
}
|
|
51
|
+
return data
|
|
52
|
+
}
|
|
53
|
+
}
|
|
3
54
|
|
|
4
55
|
// ─── Collection factory ──────────────────────────────────
|
|
5
56
|
|
|
@@ -24,7 +75,10 @@ export function createChatMessagesCollection(slugs: CollectionSlugs): Collection
|
|
|
24
75
|
}
|
|
25
76
|
return false
|
|
26
77
|
},
|
|
27
|
-
|
|
78
|
+
// Staff and support-clients only — NOT "any authenticated principal":
|
|
79
|
+
// a user of any other auth collection of the host app satisfied `!!req.user`.
|
|
80
|
+
create: ({ req }) =>
|
|
81
|
+
req.user?.collection === slugs.users || req.user?.collection === slugs.supportClients,
|
|
28
82
|
update: ({ req }) => req.user?.collection === slugs.users,
|
|
29
83
|
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
30
84
|
},
|
|
@@ -93,6 +147,9 @@ export function createChatMessagesCollection(slugs: CollectionSlugs): Collection
|
|
|
93
147
|
},
|
|
94
148
|
},
|
|
95
149
|
],
|
|
150
|
+
hooks: {
|
|
151
|
+
beforeChange: [createRestrictClientChatWrite(slugs)],
|
|
152
|
+
},
|
|
96
153
|
timestamps: true,
|
|
97
154
|
}
|
|
98
155
|
}
|
|
@@ -122,11 +122,17 @@ export function createClientSummariesCollection(slugs: CollectionSlugs): Collect
|
|
|
122
122
|
admin: { readOnly: true },
|
|
123
123
|
},
|
|
124
124
|
],
|
|
125
|
+
// Staff-only, on the SAME source of truth as every other collection and as
|
|
126
|
+
// `requireAdmin`: `slugs.users`. The literal `'users'` this used to compare
|
|
127
|
+
// against is the DEFAULT slug, not the configured one — on a host app whose
|
|
128
|
+
// staff collection is renamed (`collectionSlugs.users: 'admins'`) it named
|
|
129
|
+
// the front-office collection instead, opening read/create/update/delete on
|
|
130
|
+
// AI-generated client intelligence to it while locking the real agents out.
|
|
125
131
|
access: {
|
|
126
|
-
create: ({ req }) => req.user?.collection ===
|
|
127
|
-
read: ({ req }) => req.user?.collection ===
|
|
128
|
-
update: ({ req }) => req.user?.collection ===
|
|
129
|
-
delete: ({ req }) => req.user?.collection ===
|
|
132
|
+
create: ({ req }) => req.user?.collection === slugs.users,
|
|
133
|
+
read: ({ req }) => req.user?.collection === slugs.users,
|
|
134
|
+
update: ({ req }) => req.user?.collection === slugs.users,
|
|
135
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
130
136
|
},
|
|
131
137
|
timestamps: true,
|
|
132
138
|
}
|
|
@@ -91,12 +91,33 @@ function createEnforce2FA(slugs: CollectionSlugs): CollectionBeforeLoginHook {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Field-level write guard for the commercial / security fields.
|
|
96
|
+
*
|
|
97
|
+
* `access.update` on this collection intentionally lets a support-client PATCH
|
|
98
|
+
* their OWN document (the portal profile page writes firstName, phone, the
|
|
99
|
+
* notification toggles and the 2FA switch). Payload persists every other field of
|
|
100
|
+
* that same PATCH too, so without a field guard a client could self-promote
|
|
101
|
+
* (`tier`), rewrite the sales notes, or — worst — set `googleId`, which the Google
|
|
102
|
+
* OAuth callback resolves BEFORE the email and would hand them another account.
|
|
103
|
+
*
|
|
104
|
+
* Internal writers (2FA endpoints, OAuth linking, beforeLogin) all go through
|
|
105
|
+
* `overrideAccess: true`, which bypasses field access, so they are unaffected.
|
|
106
|
+
* `twoFactorEnabled` is deliberately NOT locked: it is a legitimate self-service
|
|
107
|
+
* toggle on the portal profile page.
|
|
108
|
+
*/
|
|
109
|
+
function createAdminOnlyFieldUpdate(slugs: CollectionSlugs) {
|
|
110
|
+
return ({ req }: { req: { user?: { collection?: string } | null } }) =>
|
|
111
|
+
req.user?.collection === slugs.users
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
// ─── Collection factory ──────────────────────────────────
|
|
95
115
|
|
|
96
116
|
export function createSupportClientsCollection(
|
|
97
117
|
slugs: CollectionSlugs,
|
|
98
118
|
options?: { capabilities?: SupportCapabilities },
|
|
99
119
|
): CollectionConfig {
|
|
120
|
+
const adminOnlyFieldUpdate = createAdminOnlyFieldUpdate(slugs)
|
|
100
121
|
return {
|
|
101
122
|
slug: slugs.supportClients,
|
|
102
123
|
labels: {
|
|
@@ -226,6 +247,7 @@ export function createSupportClientsCollection(
|
|
|
226
247
|
access: {
|
|
227
248
|
// Admin-only: never serialize commercial notes onto the client's own doc.
|
|
228
249
|
read: ({ req }) => req.user?.collection === slugs.users,
|
|
250
|
+
update: adminOnlyFieldUpdate,
|
|
229
251
|
},
|
|
230
252
|
admin: {
|
|
231
253
|
description: 'Notes commerciales (visible uniquement par les admins)',
|
|
@@ -235,6 +257,7 @@ export function createSupportClientsCollection(
|
|
|
235
257
|
name: 'tier',
|
|
236
258
|
type: 'select',
|
|
237
259
|
label: 'Plan',
|
|
260
|
+
access: { update: adminOnlyFieldUpdate },
|
|
238
261
|
options: [
|
|
239
262
|
{ label: 'Free', value: 'free' },
|
|
240
263
|
{ label: 'Basic', value: 'basic' },
|
|
@@ -248,6 +271,7 @@ export function createSupportClientsCollection(
|
|
|
248
271
|
type: 'relationship',
|
|
249
272
|
relationTo: slugs.users,
|
|
250
273
|
label: 'Agent referent',
|
|
274
|
+
access: { update: adminOnlyFieldUpdate },
|
|
251
275
|
admin: { position: 'sidebar' },
|
|
252
276
|
},
|
|
253
277
|
{
|
|
@@ -263,17 +287,20 @@ export function createSupportClientsCollection(
|
|
|
263
287
|
{
|
|
264
288
|
name: 'twoFactorCode',
|
|
265
289
|
type: 'text',
|
|
290
|
+
access: { update: adminOnlyFieldUpdate },
|
|
266
291
|
admin: { hidden: true },
|
|
267
292
|
},
|
|
268
293
|
{
|
|
269
294
|
name: 'twoFactorExpiry',
|
|
270
295
|
type: 'date',
|
|
296
|
+
access: { update: adminOnlyFieldUpdate },
|
|
271
297
|
admin: { hidden: true },
|
|
272
298
|
},
|
|
273
299
|
{
|
|
274
300
|
// Set by /support/2fa "verify"; gates login via the beforeLogin hook.
|
|
275
301
|
name: 'twoFactorVerifiedAt',
|
|
276
302
|
type: 'date',
|
|
303
|
+
access: { update: adminOnlyFieldUpdate },
|
|
277
304
|
admin: { hidden: true },
|
|
278
305
|
},
|
|
279
306
|
{
|
|
@@ -281,6 +308,7 @@ export function createSupportClientsCollection(
|
|
|
281
308
|
name: 'googleId',
|
|
282
309
|
type: 'text',
|
|
283
310
|
index: true,
|
|
311
|
+
access: { update: adminOnlyFieldUpdate },
|
|
284
312
|
admin: { hidden: true },
|
|
285
313
|
},
|
|
286
314
|
{
|
|
@@ -352,6 +380,7 @@ export function createSupportClientsCollection(
|
|
|
352
380
|
access: {
|
|
353
381
|
// Admin-only: never serialize internal notes onto the client's own doc.
|
|
354
382
|
read: ({ req }) => req.user?.collection === slugs.users,
|
|
383
|
+
update: adminOnlyFieldUpdate,
|
|
355
384
|
},
|
|
356
385
|
admin: {
|
|
357
386
|
description: 'Visible uniquement par les admins',
|
|
@@ -32,7 +32,12 @@ export function createTicketCollaboratorsCollection(slugs: CollectionSlugs): Col
|
|
|
32
32
|
}
|
|
33
33
|
return false
|
|
34
34
|
},
|
|
35
|
-
|
|
35
|
+
// Staff only. This collection is the source of truth for
|
|
36
|
+
// resolveAccessibleTicketIds(), which widens `read` on tickets, messages
|
|
37
|
+
// and the activity log — so letting any authenticated principal POST here
|
|
38
|
+
// let a support client grant themselves access to any ticket by id.
|
|
39
|
+
// The /invite endpoint is unaffected: it writes with overrideAccess: true.
|
|
40
|
+
create: ({ req }) => req.user?.collection === slugs.users,
|
|
36
41
|
update: () => false,
|
|
37
42
|
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
38
43
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { CollectionConfig, CollectionBeforeChangeHook, CollectionAfterChangeHook, Where } from 'payload'
|
|
2
|
+
import { APIError } from 'payload'
|
|
2
3
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
4
|
import type { SupportCapabilities } from '../types'
|
|
4
5
|
import { escapeHtml, emailWrapper, emailButton, emailQuote, emailParagraph, emailRichContent, emailTrackingPixel } from '../utils/emailTemplate'
|
|
5
|
-
import { fireWebhooks } from '../utils/fireWebhooks'
|
|
6
6
|
import { createAdminNotification } from '../utils/adminNotification'
|
|
7
7
|
import { dispatchWebhook } from '../utils/webhookDispatcher'
|
|
8
8
|
import { readSupportSettings } from '../utils/readSettings'
|
|
@@ -13,6 +13,41 @@ import { queueClientNotification } from '../utils/notificationQueue'
|
|
|
13
13
|
import { sendPushToUser } from '../utils/push'
|
|
14
14
|
import { dbFind, dbCreate, dbFindByID } from '../utils/db'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Cross-tenant write guard for support-clients.
|
|
18
|
+
*
|
|
19
|
+
* `access.create` only checks WHICH collection the actor belongs to, so any
|
|
20
|
+
* authenticated support-client could POST a message onto an arbitrary ticket id.
|
|
21
|
+
* That is not just a data-integrity issue: the `afterChange` notification hooks
|
|
22
|
+
* would then mail the injected body to the real ticket owner from the legitimate
|
|
23
|
+
* support address (phishing over a trusted channel).
|
|
24
|
+
*
|
|
25
|
+
* We reuse `resolveAccessibleTicketIds()` — the same boundary `access.read` uses —
|
|
26
|
+
* so read and write scopes cannot drift apart. Staff writes and every internal
|
|
27
|
+
* `overrideAccess` write are untouched: they carry no `support-clients` user.
|
|
28
|
+
*/
|
|
29
|
+
function createRestrictClientTicketTarget(slugs: CollectionSlugs): CollectionBeforeChangeHook {
|
|
30
|
+
return async ({ data, operation, req, originalDoc }) => {
|
|
31
|
+
if (req.user?.collection !== slugs.supportClients) return data
|
|
32
|
+
|
|
33
|
+
const raw = (data as { ticket?: unknown }).ticket
|
|
34
|
+
?? (operation === 'update' ? (originalDoc as { ticket?: unknown } | undefined)?.ticket : undefined)
|
|
35
|
+
const targetId = raw && typeof raw === 'object' ? (raw as { id?: unknown }).id : raw
|
|
36
|
+
if (targetId === undefined || targetId === null || targetId === '') {
|
|
37
|
+
throw new APIError('Ticket cible requis.', 400)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 'write' scope: a collaborator invited as `viewer` may READ the thread but
|
|
41
|
+
// must not post into it — the invitation email promises exactly that, and a
|
|
42
|
+
// message from a viewer would fan out the whole notification chain.
|
|
43
|
+
const accessible = await resolveAccessibleTicketIds(req.payload, slugs, req.user.id, 'write')
|
|
44
|
+
if (!accessible.some((id) => String(id) === String(targetId))) {
|
|
45
|
+
throw new APIError('Ticket inaccessible.', 403)
|
|
46
|
+
}
|
|
47
|
+
return data
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
16
51
|
function createAssignAuthor(slugs: CollectionSlugs): CollectionBeforeChangeHook {
|
|
17
52
|
return async ({ data, operation, req }) => {
|
|
18
53
|
if (operation === 'create' && req.user?.collection === slugs.supportClients) {
|
|
@@ -300,26 +335,12 @@ function createNotifyAdminOnClientMessage(slugs: CollectionSlugs, notificationSl
|
|
|
300
335
|
}
|
|
301
336
|
}
|
|
302
337
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (doc.isInternal) return doc
|
|
310
|
-
|
|
311
|
-
const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
|
|
312
|
-
fireWebhooks(req.payload, slugs, 'ticket_replied', {
|
|
313
|
-
ticketId,
|
|
314
|
-
messageId: doc.id,
|
|
315
|
-
authorType: doc.authorType,
|
|
316
|
-
body: doc.body?.length > 500 ? doc.body.slice(0, 500) + '...' : doc.body,
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
return doc
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Single outbound-webhook dispatcher for replies. See the matching comment in
|
|
340
|
+
* Tickets.ts: a second, unsigned dispatcher used to fire on this exact hook with
|
|
341
|
+
* the same guards, doubling every delivery. The `body` excerpt of the legacy
|
|
342
|
+
* payload is carried over so existing consumers do not regress.
|
|
343
|
+
*/
|
|
323
344
|
function createDispatchWebhookOnReply(slugs: CollectionSlugs): CollectionAfterChangeHook {
|
|
324
345
|
return async ({ doc, operation, req }) => {
|
|
325
346
|
if (operation !== 'create') return doc
|
|
@@ -328,7 +349,12 @@ function createDispatchWebhookOnReply(slugs: CollectionSlugs): CollectionAfterCh
|
|
|
328
349
|
|
|
329
350
|
const ticketId = typeof doc.ticket === 'object' ? doc.ticket.id : doc.ticket
|
|
330
351
|
dispatchWebhook(
|
|
331
|
-
{
|
|
352
|
+
{
|
|
353
|
+
ticketId,
|
|
354
|
+
messageId: doc.id,
|
|
355
|
+
authorType: doc.authorType,
|
|
356
|
+
body: doc.body?.length > 500 ? doc.body.slice(0, 500) + '...' : doc.body,
|
|
357
|
+
},
|
|
332
358
|
'ticket_replied',
|
|
333
359
|
req.payload,
|
|
334
360
|
slugs,
|
|
@@ -534,7 +560,7 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
|
|
|
534
560
|
] : []),
|
|
535
561
|
],
|
|
536
562
|
hooks: {
|
|
537
|
-
beforeChange: [createSanitizeMessageHtml(), createResolveMentions(slugs), createAssignAuthor(slugs)],
|
|
563
|
+
beforeChange: [createRestrictClientTicketTarget(slugs), createSanitizeMessageHtml(), createResolveMentions(slugs), createAssignAuthor(slugs)],
|
|
538
564
|
afterChange: [
|
|
539
565
|
...(options?.capabilities?.sms ? [createSmsNotification(slugs, options.capabilities.sms)] : []),
|
|
540
566
|
createAutoUpdateStatus(slugs),
|
|
@@ -543,7 +569,6 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
|
|
|
543
569
|
createCheckSlaOnReply(slugs, notificationSlug),
|
|
544
570
|
createSyncTicketReplyToChat(slugs),
|
|
545
571
|
createNotifyAdminOnClientMessage(slugs, notificationSlug),
|
|
546
|
-
createFireMessageWebhooks(slugs),
|
|
547
572
|
createDispatchWebhookOnReply(slugs),
|
|
548
573
|
createNotifyMentions(slugs),
|
|
549
574
|
...(options?.capabilities?.threadCleanup ? [createThreadCleanup(options.capabilities.threadCleanup)] : []),
|