@nlabs/reaktor 1.19.0 → 1.20.0-reactorbox.1
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 +290 -0
- package/docs/GENERATION.md +4 -0
- package/lib/actions/apps.js +17 -2
- package/lib/actions/contentGeneration.d.ts +19 -1
- package/lib/actions/contentGeneration.js +59 -24
- package/lib/actions/contentGenerationSources.d.ts +29 -0
- package/lib/actions/contentGenerationSources.js +103 -0
- package/lib/actions/crm.d.ts +6 -2
- package/lib/actions/crm.js +91 -12
- package/lib/actions/crmCanonicalOutcomes.d.ts +88 -0
- package/lib/actions/crmCanonicalOutcomes.js +398 -0
- package/lib/actions/crmCanonicalPolicy.d.ts +92 -0
- package/lib/actions/crmCanonicalPolicy.js +104 -0
- package/lib/actions/crmCheckoutRecovery.d.ts +72 -0
- package/lib/actions/crmCheckoutRecovery.js +413 -0
- package/lib/actions/crmCheckoutRecoveryPolicy.d.ts +38 -0
- package/lib/actions/crmCheckoutRecoveryPolicy.js +61 -0
- package/lib/actions/crmDeliveryEvents.d.ts +24 -0
- package/lib/actions/crmDeliveryEvents.js +207 -0
- package/lib/actions/crmDeliveryEvidence.d.ts +17 -0
- package/lib/actions/crmDeliveryEvidence.js +58 -0
- package/lib/actions/crmExecution.d.ts +125 -0
- package/lib/actions/crmExecution.js +830 -0
- package/lib/actions/crmFinancialEvidence.d.ts +12 -0
- package/lib/actions/crmFinancialEvidence.js +60 -0
- package/lib/actions/crmInsights.d.ts +60 -0
- package/lib/actions/crmInsights.js +225 -0
- package/lib/actions/crmLeadFollowups.d.ts +66 -0
- package/lib/actions/crmLeadFollowups.js +209 -0
- package/lib/actions/crmOutcomes.d.ts +106 -0
- package/lib/actions/crmOutcomes.js +289 -0
- package/lib/actions/crmRecoveryRetention.d.ts +13 -0
- package/lib/actions/crmRecoveryRetention.js +163 -0
- package/lib/actions/crmReminderDispatch.d.ts +10 -0
- package/lib/actions/crmReminderDispatch.js +65 -0
- package/lib/actions/crmSourceOwnership.d.ts +7 -0
- package/lib/actions/crmSourceOwnership.js +27 -0
- package/lib/actions/crmSupportInsights.d.ts +18 -0
- package/lib/actions/crmSupportInsights.js +47 -0
- package/lib/actions/crmWinback.d.ts +71 -0
- package/lib/actions/crmWinback.js +347 -0
- package/lib/actions/crmWinbackConsent.d.ts +29 -0
- package/lib/actions/crmWinbackConsent.js +158 -0
- package/lib/actions/crmWinbackPolicy.d.ts +33 -0
- package/lib/actions/crmWinbackPolicy.js +115 -0
- package/lib/actions/crmWinbackShopSource.d.ts +15 -0
- package/lib/actions/crmWinbackShopSource.js +63 -0
- package/lib/actions/email.d.ts +4 -1
- package/lib/actions/email.js +25 -3
- package/lib/actions/index.d.ts +56 -0
- package/lib/actions/index.js +42 -1
- package/lib/actions/merchantAccountExperiment.d.ts +50 -0
- package/lib/actions/merchantAccountExperiment.js +424 -0
- package/lib/actions/merchantAccountExperimentConfig.d.ts +7 -0
- package/lib/actions/merchantAccountExperimentConfig.js +32 -0
- package/lib/actions/merchantAccountExperimentHost.d.ts +69 -0
- package/lib/actions/merchantAccountExperimentHost.js +92 -0
- package/lib/actions/merchantAccountExperimentPolicy.d.ts +66 -0
- package/lib/actions/merchantAccountExperimentPolicy.js +121 -0
- package/lib/actions/merchantAccountExperimentRelay.d.ts +10 -0
- package/lib/actions/merchantAccountExperimentRelay.js +75 -0
- package/lib/actions/merchantCalendar.d.ts +34 -0
- package/lib/actions/merchantCalendar.js +119 -0
- package/lib/actions/merchantCalendarBookings.d.ts +17 -0
- package/lib/actions/merchantCalendarBookings.js +113 -0
- package/lib/actions/merchantCalendarDelegation.d.ts +80 -0
- package/lib/actions/merchantCalendarDelegation.js +353 -0
- package/lib/actions/merchantCalendarDiscovery.d.ts +38 -0
- package/lib/actions/merchantCalendarDiscovery.js +124 -0
- package/lib/actions/merchantCalendarEventHost.d.ts +105 -0
- package/lib/actions/merchantCalendarEventHost.js +16 -0
- package/lib/actions/merchantCalendarEventPrivacy.d.ts +7 -0
- package/lib/actions/merchantCalendarEventPrivacy.js +70 -0
- package/lib/actions/merchantCalendarEventRepository.d.ts +121 -0
- package/lib/actions/merchantCalendarEventRepository.js +295 -0
- package/lib/actions/merchantCalendarEventTransport.d.ts +65 -0
- package/lib/actions/merchantCalendarEventTransport.js +132 -0
- package/lib/actions/merchantCalendarEvents.d.ts +172 -0
- package/lib/actions/merchantCalendarEvents.js +365 -0
- package/lib/actions/merchantCalendarInvitationConsent.d.ts +28 -0
- package/lib/actions/merchantCalendarInvitationConsent.js +118 -0
- package/lib/actions/merchantCalendarManagedSource.d.ts +22 -0
- package/lib/actions/merchantCalendarManagedSource.js +24 -0
- package/lib/actions/merchantCalendarOAuth.d.ts +92 -0
- package/lib/actions/merchantCalendarOAuth.js +502 -0
- package/lib/actions/merchantCalendarOAuthConfig.d.ts +4 -0
- package/lib/actions/merchantCalendarOAuthConfig.js +30 -0
- package/lib/actions/merchantCalendarOAuthRepository.d.ts +57 -0
- package/lib/actions/merchantCalendarOAuthRepository.js +108 -0
- package/lib/actions/merchantCalendarOAuthTransport.d.ts +4 -0
- package/lib/actions/merchantCalendarOAuthTransport.js +25 -0
- package/lib/actions/merchantCalendarScopes.d.ts +4 -0
- package/lib/actions/merchantCalendarScopes.js +31 -0
- package/lib/actions/merchantFinance.d.ts +65 -0
- package/lib/actions/merchantFinance.js +244 -0
- package/lib/actions/merchantFinanceProvider.d.ts +57 -0
- package/lib/actions/merchantFinanceProvider.js +180 -0
- package/lib/actions/merchantOperations.d.ts +94 -0
- package/lib/actions/merchantOperations.js +489 -0
- package/lib/actions/providerCredentials.d.ts +35 -0
- package/lib/actions/providerCredentials.js +166 -0
- package/lib/actions/s3.d.ts +10 -7
- package/lib/actions/s3.js +59 -14
- package/lib/actions/supportChannelSources.d.ts +66 -0
- package/lib/actions/supportChannelSources.js +183 -0
- package/lib/actions/supportCommerce.d.ts +62 -0
- package/lib/actions/supportCommerce.js +252 -0
- package/lib/actions/supportConnect.d.ts +39 -0
- package/lib/actions/supportConnect.js +224 -0
- package/lib/actions/supportContext.d.ts +31 -0
- package/lib/actions/supportContext.js +88 -0
- package/lib/actions/supportConversation.d.ts +102 -0
- package/lib/actions/supportConversation.js +100 -0
- package/lib/actions/supportEmailEvents.d.ts +20 -0
- package/lib/actions/supportEmailEvents.js +130 -0
- package/lib/actions/supportEmailHandoff.d.ts +114 -0
- package/lib/actions/supportEmailHandoff.js +356 -0
- package/lib/actions/supportOperations.d.ts +32 -0
- package/lib/actions/supportOperations.js +74 -0
- package/lib/actions/supportPolicy.d.ts +9 -0
- package/lib/actions/supportPolicy.js +44 -0
- package/lib/actions/supportPurposeConsent.d.ts +32 -0
- package/lib/actions/supportPurposeConsent.js +196 -0
- package/lib/actions/supportPurposePrivacy.d.ts +15 -0
- package/lib/actions/supportPurposePrivacy.js +181 -0
- package/lib/actions/supportSemanticReview.d.ts +64 -0
- package/lib/actions/supportSemanticReview.js +239 -0
- package/lib/actions/users.js +22 -3
- package/lib/constants/collections.d.ts +2 -1
- package/lib/constants/collections.js +2 -1
- package/lib/mutations/crm/crm.js +395 -7
- package/lib/queries/crm/crm.js +251 -5
- package/lib/types/auth.types.d.ts +2 -0
- package/lib/types/crm.types.d.ts +6 -0
- package/lib/types/users.types.d.ts +2 -0
- package/lib/utils/authUtils.js +17 -1
- package/lib/utils/graphqlUtils.d.ts +2 -0
- package/lib/utils/stripeUtils.ts.bak +1 -1
- package/package.json +8 -6
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle -- Native document identity and revision checks. */ import { aql } from 'arangojs';
|
|
2
|
+
import { scopeContextToApp } from '../utils/appContext.js';
|
|
3
|
+
import { useDb } from '../utils/arangodbUtils.js';
|
|
4
|
+
import { crmCanonicalDigest } from './crmCanonicalPolicy.js';
|
|
5
|
+
import { authorizeCrmExecutionGrant } from './crmExecution.js';
|
|
6
|
+
export const supportConversationKey = (value)=>typeof value === 'string' && /^[\w.-]{1,160}$/.test(value);
|
|
7
|
+
export const validSupportConversationInput = (input)=>Boolean(input && Object.keys(input).sort().join(',') === 'appId,ticketId,ticketRevision' && [
|
|
8
|
+
input.appId,
|
|
9
|
+
input.ticketId,
|
|
10
|
+
input.ticketRevision
|
|
11
|
+
].every(supportConversationKey));
|
|
12
|
+
export const readSupportConversationRow = async (db, input, purpose, query = db.query.bind(db))=>query(aql`
|
|
13
|
+
LET ticket = DOCUMENT('supportTickets', ${input.ticketId})
|
|
14
|
+
LET user = ticket ? DOCUMENT('users', ticket.userId) : null
|
|
15
|
+
LET matchingConsent = (FOR candidate IN supportPurposeConsents FILTER candidate.appId == ${input.appId}
|
|
16
|
+
AND candidate.userId == user._key AND candidate.purpose == ${purpose} LIMIT 2 RETURN candidate)
|
|
17
|
+
LET messages = (FOR message IN supportTicketMessages FILTER message.supportTicketId == ${input.ticketId}
|
|
18
|
+
AND (message.appId == null OR message.appId == ${input.appId}) AND message.visibility == 'customer' AND
|
|
19
|
+
message.deleted != true
|
|
20
|
+
SORT message.modified ASC, message._key ASC LIMIT 51
|
|
21
|
+
LET bindings = (FOR edge IN hasSupportTicketMessage FILTER edge._from == ${`supportTickets/${input.ticketId}`}
|
|
22
|
+
AND edge._to == CONCAT('supportTicketMessages/', message._key) AND edge.visibility == 'customer' LIMIT 2
|
|
23
|
+
RETURN edge._key)
|
|
24
|
+
RETURN {bindingCount: LENGTH(bindings), _key: message._key, _rev: message._rev, appId: message.appId, body:
|
|
25
|
+
message.body,
|
|
26
|
+
deleted: message.deleted, modified: message.modified, supportTicketId: message.supportTicketId,
|
|
27
|
+
visibility: message.visibility})
|
|
28
|
+
RETURN {consent: LENGTH(matchingConsent) == 1 ? matchingConsent[0] : null, messages,
|
|
29
|
+
ticket: ticket ? {_key: ticket._key, _rev: ticket._rev, appId: ticket.appId, deleted: ticket.deleted,
|
|
30
|
+
status: ticket.status, userId: ticket.userId} : null,
|
|
31
|
+
user: user ? {_key: user._key, _rev: user._rev, active: user.active, appId: user.appId, deleted: user.deleted,
|
|
32
|
+
email: user.email, verifiedEmail: user.verifiedEmail, emailDeliveryStatus: user.emailDeliveryStatus, supportEmailSuppressed:
|
|
33
|
+
user.supportEmailSuppressed} : null}`).then((cursor)=>cursor.next());
|
|
34
|
+
export const validateSupportConversationRow = (row, input, purpose, at)=>{
|
|
35
|
+
const { consent, ticket, user } = row || {};
|
|
36
|
+
if (!ticket || ticket._key !== input.ticketId || ticket._rev !== input.ticketRevision || ticket.appId !== input.appId || ticket.deleted === true || ![
|
|
37
|
+
'submitted',
|
|
38
|
+
'open',
|
|
39
|
+
'in_progress',
|
|
40
|
+
'waiting_on_customer',
|
|
41
|
+
'waiting_on_internal'
|
|
42
|
+
].includes(ticket.status) || !user || user._key !== ticket.userId || !supportConversationKey(user._rev) || user.appId !== input.appId || user.deleted === true || user.active === false || !consent || consent.appId !== input.appId || consent.userId !== user._key || !supportConversationKey(consent._rev) || consent.purpose !== purpose || consent.source !== 'authenticated_customer' || consent.status !== 'active' || !Number.isSafeInteger(consent.expiresAt) || consent.expiresAt <= at || !Array.isArray(row.messages) || row.messages.some((message)=>!supportConversationKey(message._key) || !supportConversationKey(message._rev) || message.appId !== undefined && message.appId !== input.appId || message.bindingCount !== 1 || message.deleted === true || message.supportTicketId !== input.ticketId || message.visibility !== 'customer' || typeof message.body !== 'string' || message.body.length > 4000 || !Number.isSafeInteger(message.modified) || message.modified < 0 || message.modified > at)) {
|
|
43
|
+
throw new Error('support_conversation_source_hold');
|
|
44
|
+
}
|
|
45
|
+
return crmCanonicalDigest([
|
|
46
|
+
ticket._rev,
|
|
47
|
+
user._rev,
|
|
48
|
+
consent._rev,
|
|
49
|
+
row.messages.map((message)=>[
|
|
50
|
+
message._key,
|
|
51
|
+
message._rev
|
|
52
|
+
])
|
|
53
|
+
]);
|
|
54
|
+
};
|
|
55
|
+
export const supportPurposeScope = {
|
|
56
|
+
support_conversation: 'support:conversation',
|
|
57
|
+
support_email_reply: 'support:email_reply',
|
|
58
|
+
support_semantic_summary: 'support:summary'
|
|
59
|
+
};
|
|
60
|
+
/** The bounded native customer-visible feed is an owned source, never an external transcript assertion. */ export const readSupportPurposeConversation = async (context, input, purpose, now = Date.now)=>{
|
|
61
|
+
if (!validSupportConversationInput(input)) {
|
|
62
|
+
throw new Error('invalid_support_conversation');
|
|
63
|
+
}
|
|
64
|
+
// These new scopes are added to the public grant contract during coordinated package wiring.
|
|
65
|
+
const grant = await authorizeCrmExecutionGrant(context, input.appId, supportPurposeScope[purpose], now());
|
|
66
|
+
await authorizeCrmExecutionGrant(context, input.appId, 'support:read', now());
|
|
67
|
+
const scoped = await scopeContextToApp(context, input.appId);
|
|
68
|
+
if (scoped.databaseName !== grant.databaseName) {
|
|
69
|
+
throw new Error('support_conversation_app_changed');
|
|
70
|
+
}
|
|
71
|
+
const db = useDb(grant.databaseName);
|
|
72
|
+
const row = await readSupportConversationRow(db, input, purpose);
|
|
73
|
+
const revision = validateSupportConversationRow(row, input, purpose, now());
|
|
74
|
+
const latest = await authorizeCrmExecutionGrant(context, input.appId, supportPurposeScope[purpose], now());
|
|
75
|
+
const scope = await scopeContextToApp(context, input.appId);
|
|
76
|
+
if (grant.id !== latest.id || grant.hash !== latest.hash || grant.databaseName !== latest.databaseName || scope.databaseName !== grant.databaseName || validateSupportConversationRow(await readSupportConversationRow(db, input, purpose), input, purpose, now()) !== revision) {
|
|
77
|
+
throw new Error('support_conversation_changed');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
appId: input.appId,
|
|
81
|
+
generatedAt: now(),
|
|
82
|
+
messages: row.messages.slice(0, 50).map((message)=>({
|
|
83
|
+
body: message.body,
|
|
84
|
+
id: message._key,
|
|
85
|
+
modifiedAt: message.modified,
|
|
86
|
+
revision: message._rev
|
|
87
|
+
})),
|
|
88
|
+
provenance: 'native_customer_visible_feed',
|
|
89
|
+
revision,
|
|
90
|
+
schemaVersion: 'support_conversation_v1',
|
|
91
|
+
ticketId: input.ticketId,
|
|
92
|
+
ticketRevision: input.ticketRevision,
|
|
93
|
+
truncated: row.messages.length > 50,
|
|
94
|
+
validUntil: Math.min(now() + 300000, latest.expires, row.consent.expiresAt)
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export const readSupportConversation = async (context, input, now = Date.now)=>readSupportPurposeConversation(context, input, 'support_conversation', now);
|
|
98
|
+
export const readSupportSemanticSource = async (context, input, now = Date.now)=>readSupportPurposeConversation(context, input, 'support_semantic_summary', now);
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi90bXAvcmVhY3RvcmJveC1zdXBwb3J0LW93bmVyLWZyZWV6ZS0yMDI2MTAwOC9pbnB1dC9hY3Rpb25zL3N1cHBvcnRDb252ZXJzYXRpb24udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgbm8tdW5kZXJzY29yZS1kYW5nbGUgLS0gTmF0aXZlIGRvY3VtZW50IGlkZW50aXR5IGFuZCByZXZpc2lvbiBjaGVja3MuICovXG5pbXBvcnQge2FxbH0gZnJvbSAnYXJhbmdvanMnO1xuXG5pbXBvcnQge3Njb3BlQ29udGV4dFRvQXBwfSBmcm9tICcuLi91dGlscy9hcHBDb250ZXh0LmpzJztcbmltcG9ydCB7dXNlRGJ9IGZyb20gJy4uL3V0aWxzL2FyYW5nb2RiVXRpbHMuanMnO1xuaW1wb3J0IHtjcm1DYW5vbmljYWxEaWdlc3R9IGZyb20gJy4vY3JtQ2Fub25pY2FsUG9saWN5LmpzJztcbmltcG9ydCB7YXV0aG9yaXplQ3JtRXhlY3V0aW9uR3JhbnR9IGZyb20gJy4vY3JtRXhlY3V0aW9uLmpzJztcblxuaW1wb3J0IHR5cGUge0FwaUNvbnRleHR9IGZyb20gJy4uL3R5cGVzL2F1dGgudHlwZXMuanMnO1xuaW1wb3J0IHR5cGUge1N1cHBvcnRDb25zZW50UHVycG9zZSwgU3VwcG9ydFB1cnBvc2VDb25zZW50UmVjb3JkfSBmcm9tICcuL3N1cHBvcnRQdXJwb3NlQ29uc2VudC5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3VwcG9ydENvbnZlcnNhdGlvbklucHV0IHtcbiAgYXBwSWQ6IHN0cmluZztcbiAgdGlja2V0SWQ6IHN0cmluZztcbiAgdGlja2V0UmV2aXNpb246IHN0cmluZztcbn1cbmV4cG9ydCBjb25zdCBzdXBwb3J0Q29udmVyc2F0aW9uS2V5ID0gKHZhbHVlOiB1bmtub3duKTogdmFsdWUgaXMgc3RyaW5nID0+IHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgJiZcbiAgL15bXFx3Li1dezEsMTYwfSQvLnRlc3QodmFsdWUpO1xuZXhwb3J0IGNvbnN0IHZhbGlkU3VwcG9ydENvbnZlcnNhdGlvbklucHV0ID0gKGlucHV0OiBTdXBwb3J0Q29udmVyc2F0aW9uSW5wdXQpID0+IEJvb2xlYW4oaW5wdXRcbiAgICAmJiBPYmplY3Qua2V5cyhpbnB1dCkuc29ydCgpLmpvaW4oJywnKSA9PT0gJ2FwcElkLHRpY2tldElkLHRpY2tldFJldmlzaW9uJ1xuICAgICYmIFtpbnB1dC5hcHBJZCwgaW5wdXQudGlja2V0SWQsIGlucHV0LnRpY2tldFJldmlzaW9uXS5ldmVyeShzdXBwb3J0Q29udmVyc2F0aW9uS2V5KSk7XG5leHBvcnQgaW50ZXJmYWNlIFN1cHBvcnRDb252ZXJzYXRpb25Sb3cge1xuICBjb25zZW50PzogU3VwcG9ydFB1cnBvc2VDb25zZW50UmVjb3JkO1xuICBtZXNzYWdlczoge1xuICAgIF9rZXk6IHN0cmluZztcbiAgICBfcmV2OiBzdHJpbmc7XG4gICAgYXBwSWQ/OiBzdHJpbmc7XG4gICAgYmluZGluZ0NvdW50OiBudW1iZXI7XG4gICAgYm9keTogc3RyaW5nO1xuICAgIGRlbGV0ZWQ/OiBib29sZWFuO1xuICAgIG1vZGlmaWVkOiBudW1iZXI7XG4gICAgc3VwcG9ydFRpY2tldElkOiBzdHJpbmc7XG4gICAgdmlzaWJpbGl0eTogc3RyaW5nO1xuICB9W107XG4gIHRpY2tldD86IHtcbiAgICBfa2V5OiBzdHJpbmc7XG4gICAgX3Jldjogc3RyaW5nO1xuICAgIGFwcElkOiBzdHJpbmc7XG4gICAgZGVsZXRlZD86IGJvb2xlYW47XG4gICAgc3RhdHVzOiBzdHJpbmc7XG4gICAgdXNlcklkOiBzdHJpbmc7XG4gIH07XG4gIHVzZXI/OiB7XG4gICAgX2tleTogc3RyaW5nO1xuICAgIF9yZXY6IHN0cmluZztcbiAgICBhY3RpdmU/OiBib29sZWFuO1xuICAgIGFwcElkOiBzdHJpbmc7XG4gICAgZGVsZXRlZD86IGJvb2xlYW47XG4gICAgZW1haWw/OiBzdHJpbmc7XG4gICAgdmVyaWZpZWRFbWFpbD86IGJvb2xlYW47IGVtYWlsRGVsaXZlcnlTdGF0dXM/OiBzdHJpbmc7XG4gICAgc3VwcG9ydEVtYWlsU3VwcHJlc3NlZD86IGJvb2xlYW47XG4gIH07XG59XG5leHBvcnQgY29uc3QgcmVhZFN1cHBvcnRDb252ZXJzYXRpb25Sb3cgPSBhc3luYyAoZGI6IFJldHVyblR5cGU8dHlwZW9mIHVzZURiPiwgaW5wdXQ6IFN1cHBvcnRDb252ZXJzYXRpb25JbnB1dCxcbiAgcHVycG9zZTogU3VwcG9ydENvbnNlbnRQdXJwb3NlLCBxdWVyeSA9IGRiLnF1ZXJ5LmJpbmQoZGIpKTogUHJvbWlzZTxTdXBwb3J0Q29udmVyc2F0aW9uUm93PiA9PiBxdWVyeShhcWwgYFxuICBMRVQgdGlja2V0ID0gRE9DVU1FTlQoJ3N1cHBvcnRUaWNrZXRzJywgJHtpbnB1dC50aWNrZXRJZH0pXG4gIExFVCB1c2VyID0gdGlja2V0ID8gRE9DVU1FTlQoJ3VzZXJzJywgdGlja2V0LnVzZXJJZCkgOiBudWxsXG4gIExFVCBtYXRjaGluZ0NvbnNlbnQgPSAoRk9SIGNhbmRpZGF0ZSBJTiBzdXBwb3J0UHVycG9zZUNvbnNlbnRzIEZJTFRFUiBjYW5kaWRhdGUuYXBwSWQgPT0gJHtpbnB1dC5hcHBJZH1cbiAgICBBTkQgY2FuZGlkYXRlLnVzZXJJZCA9PSB1c2VyLl9rZXkgQU5EIGNhbmRpZGF0ZS5wdXJwb3NlID09ICR7cHVycG9zZX0gTElNSVQgMiBSRVRVUk4gY2FuZGlkYXRlKVxuICBMRVQgbWVzc2FnZXMgPSAoRk9SIG1lc3NhZ2UgSU4gc3VwcG9ydFRpY2tldE1lc3NhZ2VzIEZJTFRFUiBtZXNzYWdlLnN1cHBvcnRUaWNrZXRJZCA9PSAke2lucHV0LnRpY2tldElkfVxuICAgIEFORCAobWVzc2FnZS5hcHBJZCA9PSBudWxsIE9SIG1lc3NhZ2UuYXBwSWQgPT0gJHtpbnB1dC5hcHBJZH0pIEFORCBtZXNzYWdlLnZpc2liaWxpdHkgPT0gJ2N1c3RvbWVyJyBBTkRcbiAgICAgIG1lc3NhZ2UuZGVsZXRlZCAhPSB0cnVlXG4gICAgU09SVCBtZXNzYWdlLm1vZGlmaWVkIEFTQywgbWVzc2FnZS5fa2V5IEFTQyBMSU1JVCA1MVxuICAgIExFVCBiaW5kaW5ncyA9IChGT1IgZWRnZSBJTiBoYXNTdXBwb3J0VGlja2V0TWVzc2FnZSBGSUxURVIgZWRnZS5fZnJvbSA9PSAke2BzdXBwb3J0VGlja2V0cy8ke2lucHV0LnRpY2tldElkfWB9XG4gICAgICBBTkQgZWRnZS5fdG8gPT0gQ09OQ0FUKCdzdXBwb3J0VGlja2V0TWVzc2FnZXMvJywgbWVzc2FnZS5fa2V5KSBBTkQgZWRnZS52aXNpYmlsaXR5ID09ICdjdXN0b21lcicgTElNSVQgMlxuICAgICAgICBSRVRVUk4gZWRnZS5fa2V5KVxuICAgIFJFVFVSTiB7YmluZGluZ0NvdW50OiBMRU5HVEgoYmluZGluZ3MpLCBfa2V5OiBtZXNzYWdlLl9rZXksIF9yZXY6IG1lc3NhZ2UuX3JldiwgYXBwSWQ6IG1lc3NhZ2UuYXBwSWQsIGJvZHk6XG4gICAgICBtZXNzYWdlLmJvZHksXG4gICAgICBkZWxldGVkOiBtZXNzYWdlLmRlbGV0ZWQsIG1vZGlmaWVkOiBtZXNzYWdlLm1vZGlmaWVkLCBzdXBwb3J0VGlja2V0SWQ6IG1lc3NhZ2Uuc3VwcG9ydFRpY2tldElkLFxuICAgICAgdmlzaWJpbGl0eTogbWVzc2FnZS52aXNpYmlsaXR5fSlcbiAgUkVUVVJOIHtjb25zZW50OiBMRU5HVEgobWF0Y2hpbmdDb25zZW50KSA9PSAxID8gbWF0Y2hpbmdDb25zZW50WzBdIDogbnVsbCwgbWVzc2FnZXMsXG4gICAgdGlja2V0OiB0aWNrZXQgPyB7X2tleTogdGlja2V0Ll9rZXksIF9yZXY6IHRpY2tldC5fcmV2LCBhcHBJZDogdGlja2V0LmFwcElkLCBkZWxldGVkOiB0aWNrZXQuZGVsZXRlZCxcbiAgICAgIHN0YXR1czogdGlja2V0LnN0YXR1cywgdXNlcklkOiB0aWNrZXQudXNlcklkfSA6IG51bGwsXG4gICAgdXNlcjogdXNlciA/IHtfa2V5OiB1c2VyLl9rZXksIF9yZXY6IHVzZXIuX3JldiwgYWN0aXZlOiB1c2VyLmFjdGl2ZSwgYXBwSWQ6IHVzZXIuYXBwSWQsIGRlbGV0ZWQ6IHVzZXIuZGVsZXRlZCxcbiAgICAgIGVtYWlsOiB1c2VyLmVtYWlsLCB2ZXJpZmllZEVtYWlsOiB1c2VyLnZlcmlmaWVkRW1haWwsIGVtYWlsRGVsaXZlcnlTdGF0dXM6IHVzZXIuZW1haWxEZWxpdmVyeVN0YXR1cywgc3VwcG9ydEVtYWlsU3VwcHJlc3NlZDpcbiAgICAgICAgdXNlci5zdXBwb3J0RW1haWxTdXBwcmVzc2VkfSA6IG51bGx9YClcbiAgLnRoZW4oKGN1cnNvcikgPT4gY3Vyc29yLm5leHQoKSk7XG5leHBvcnQgY29uc3QgdmFsaWRhdGVTdXBwb3J0Q29udmVyc2F0aW9uUm93ID0gKHJvdzogU3VwcG9ydENvbnZlcnNhdGlvblJvdyB8IHVuZGVmaW5lZCwgaW5wdXQ6XG5TdXBwb3J0Q29udmVyc2F0aW9uSW5wdXQsIHB1cnBvc2U6IFN1cHBvcnRDb25zZW50UHVycG9zZSwgYXQ6IG51bWJlcikgPT4ge1xuICBjb25zdCB7Y29uc2VudCwgdGlja2V0LCB1c2VyfSA9IHJvdyB8fCB7fTtcbiAgaWYoIXRpY2tldCB8fCB0aWNrZXQuX2tleSAhPT0gaW5wdXQudGlja2V0SWQgfHwgdGlja2V0Ll9yZXYgIT09IGlucHV0LnRpY2tldFJldmlzaW9uXG4gICAgICAgIHx8IHRpY2tldC5hcHBJZCAhPT0gaW5wdXQuYXBwSWQgfHwgdGlja2V0LmRlbGV0ZWQgPT09IHRydWVcbiAgICAgICAgfHwgIVsnc3VibWl0dGVkJywgJ29wZW4nLCAnaW5fcHJvZ3Jlc3MnLCAnd2FpdGluZ19vbl9jdXN0b21lcicsICd3YWl0aW5nX29uX2ludGVybmFsJ10uaW5jbHVkZXModGlja2V0LnN0YXR1cylcbiAgICAgICAgfHwgIXVzZXIgfHwgdXNlci5fa2V5ICE9PSB0aWNrZXQudXNlcklkIHx8ICFzdXBwb3J0Q29udmVyc2F0aW9uS2V5KHVzZXIuX3JldilcbiAgICAgICAgfHwgdXNlci5hcHBJZCAhPT0gaW5wdXQuYXBwSWQgfHwgdXNlci5kZWxldGVkID09PSB0cnVlIHx8IHVzZXIuYWN0aXZlID09PSBmYWxzZVxuICAgICAgICB8fCAhY29uc2VudCB8fCBjb25zZW50LmFwcElkICE9PSBpbnB1dC5hcHBJZCB8fCBjb25zZW50LnVzZXJJZCAhPT0gdXNlci5fa2V5XG4gICAgICAgIHx8ICFzdXBwb3J0Q29udmVyc2F0aW9uS2V5KGNvbnNlbnQuX3JldikgfHwgY29uc2VudC5wdXJwb3NlICE9PSBwdXJwb3NlXG4gICAgICAgIHx8IGNvbnNlbnQuc291cmNlICE9PSAnYXV0aGVudGljYXRlZF9jdXN0b21lcicgfHwgY29uc2VudC5zdGF0dXMgIT09ICdhY3RpdmUnXG4gICAgICAgIHx8ICFOdW1iZXIuaXNTYWZlSW50ZWdlcihjb25zZW50LmV4cGlyZXNBdCkgfHwgY29uc2VudC5leHBpcmVzQXQgPD0gYXRcbiAgICAgICAgfHwgIUFycmF5LmlzQXJyYXkocm93Lm1lc3NhZ2VzKSB8fCByb3cubWVzc2FnZXMuc29tZSgobWVzc2FnZSkgPT4gIXN1cHBvcnRDb252ZXJzYXRpb25LZXkobWVzc2FnZS5fa2V5KVxuICAgICAgICB8fCAhc3VwcG9ydENvbnZlcnNhdGlvbktleShtZXNzYWdlLl9yZXYpIHx8IChtZXNzYWdlLmFwcElkICE9PSB1bmRlZmluZWQgJiYgbWVzc2FnZS5hcHBJZCAhPT0gaW5wdXQuYXBwSWQpXG4gICAgICAgICAgfHwgbWVzc2FnZS5iaW5kaW5nQ291bnQgIT09IDEgfHwgbWVzc2FnZS5kZWxldGVkID09PSB0cnVlXG4gICAgICAgIHx8IG1lc3NhZ2Uuc3VwcG9ydFRpY2tldElkICE9PSBpbnB1dC50aWNrZXRJZCB8fCBtZXNzYWdlLnZpc2liaWxpdHkgIT09ICdjdXN0b21lcidcbiAgICAgICAgfHwgdHlwZW9mIG1lc3NhZ2UuYm9keSAhPT0gJ3N0cmluZycgfHwgbWVzc2FnZS5ib2R5Lmxlbmd0aCA+IDQwMDBcbiAgICAgICAgfHwgIU51bWJlci5pc1NhZmVJbnRlZ2VyKG1lc3NhZ2UubW9kaWZpZWQpIHx8IG1lc3NhZ2UubW9kaWZpZWQgPCAwIHx8IG1lc3NhZ2UubW9kaWZpZWQgPiBhdCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ3N1cHBvcnRfY29udmVyc2F0aW9uX3NvdXJjZV9ob2xkJyk7XG4gIH1cbiAgcmV0dXJuIGNybUNhbm9uaWNhbERpZ2VzdChbdGlja2V0Ll9yZXYsIHVzZXIuX3JldiwgY29uc2VudC5fcmV2LFxuICAgIHJvdy5tZXNzYWdlcy5tYXAoKG1lc3NhZ2UpID0+IFttZXNzYWdlLl9rZXksIG1lc3NhZ2UuX3Jldl0pXSk7XG59O1xuZXhwb3J0IGNvbnN0IHN1cHBvcnRQdXJwb3NlU2NvcGUgPSB7XG4gIHN1cHBvcnRfY29udmVyc2F0aW9uOiAnc3VwcG9ydDpjb252ZXJzYXRpb24nLFxuICBzdXBwb3J0X2VtYWlsX3JlcGx5OiAnc3VwcG9ydDplbWFpbF9yZXBseScsIHN1cHBvcnRfc2VtYW50aWNfc3VtbWFyeTogJ3N1cHBvcnQ6c3VtbWFyeSdcbn0gYXMgY29uc3Q7XG4vKiogVGhlIGJvdW5kZWQgbmF0aXZlIGN1c3RvbWVyLXZpc2libGUgZmVlZCBpcyBhbiBvd25lZCBzb3VyY2UsIG5ldmVyIGFuIGV4dGVybmFsIHRyYW5zY3JpcHQgYXNzZXJ0aW9uLiAqL1xuZXhwb3J0IGNvbnN0IHJlYWRTdXBwb3J0UHVycG9zZUNvbnZlcnNhdGlvbiA9IGFzeW5jIChjb250ZXh0OiBBcGlDb250ZXh0LCBpbnB1dDogU3VwcG9ydENvbnZlcnNhdGlvbklucHV0LFxuICBwdXJwb3NlOiBTdXBwb3J0Q29uc2VudFB1cnBvc2UsIG5vdyA9IERhdGUubm93KSA9PiB7XG4gIGlmKCF2YWxpZFN1cHBvcnRDb252ZXJzYXRpb25JbnB1dChpbnB1dCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ2ludmFsaWRfc3VwcG9ydF9jb252ZXJzYXRpb24nKTtcbiAgfVxuICAvLyBUaGVzZSBuZXcgc2NvcGVzIGFyZSBhZGRlZCB0byB0aGUgcHVibGljIGdyYW50IGNvbnRyYWN0IGR1cmluZyBjb29yZGluYXRlZCBwYWNrYWdlIHdpcmluZy5cbiAgY29uc3QgZ3JhbnQgPSBhd2FpdCBhdXRob3JpemVDcm1FeGVjdXRpb25HcmFudChjb250ZXh0LCBpbnB1dC5hcHBJZCwgc3VwcG9ydFB1cnBvc2VTY29wZVtwdXJwb3NlXSwgbm93KCkpO1xuICBhd2FpdCBhdXRob3JpemVDcm1FeGVjdXRpb25HcmFudChjb250ZXh0LCBpbnB1dC5hcHBJZCwgJ3N1cHBvcnQ6cmVhZCcsIG5vdygpKTtcbiAgY29uc3Qgc2NvcGVkID0gYXdhaXQgc2NvcGVDb250ZXh0VG9BcHAoY29udGV4dCwgaW5wdXQuYXBwSWQpO1xuICBpZihzY29wZWQuZGF0YWJhc2VOYW1lICE9PSBncmFudC5kYXRhYmFzZU5hbWUpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ3N1cHBvcnRfY29udmVyc2F0aW9uX2FwcF9jaGFuZ2VkJyk7XG4gIH1cbiAgY29uc3QgZGIgPSB1c2VEYihncmFudC5kYXRhYmFzZU5hbWUpO1xuICBjb25zdCByb3cgPSBhd2FpdCByZWFkU3VwcG9ydENvbnZlcnNhdGlvblJvdyhkYiwgaW5wdXQsIHB1cnBvc2UpO1xuICBjb25zdCByZXZpc2lvbiA9IHZhbGlkYXRlU3VwcG9ydENvbnZlcnNhdGlvblJvdyhyb3csIGlucHV0LCBwdXJwb3NlLCBub3coKSk7XG4gIGNvbnN0IGxhdGVzdCA9IGF3YWl0IGF1dGhvcml6ZUNybUV4ZWN1dGlvbkdyYW50KGNvbnRleHQsIGlucHV0LmFwcElkLCBzdXBwb3J0UHVycG9zZVNjb3BlW3B1cnBvc2VdLCBub3coKSk7XG4gIGNvbnN0IHNjb3BlID0gYXdhaXQgc2NvcGVDb250ZXh0VG9BcHAoY29udGV4dCwgaW5wdXQuYXBwSWQpO1xuICBpZihncmFudC5pZCAhPT0gbGF0ZXN0LmlkIHx8IGdyYW50Lmhhc2ggIT09IGxhdGVzdC5oYXNoIHx8IGdyYW50LmRhdGFiYXNlTmFtZSAhPT0gbGF0ZXN0LmRhdGFiYXNlTmFtZVxuICAgICAgICB8fCBzY29wZS5kYXRhYmFzZU5hbWUgIT09IGdyYW50LmRhdGFiYXNlTmFtZVxuICAgICAgICB8fCB2YWxpZGF0ZVN1cHBvcnRDb252ZXJzYXRpb25Sb3coYXdhaXQgcmVhZFN1cHBvcnRDb252ZXJzYXRpb25Sb3coZGIsIGlucHV0LCBwdXJwb3NlKSwgaW5wdXQsIHB1cnBvc2UsXG4gICAgICAgICAgbm93KCkpICE9PSByZXZpc2lvbikge1xuICAgIHRocm93IG5ldyBFcnJvcignc3VwcG9ydF9jb252ZXJzYXRpb25fY2hhbmdlZCcpO1xuICB9XG4gIHJldHVybiB7XG4gICAgYXBwSWQ6IGlucHV0LmFwcElkLCBnZW5lcmF0ZWRBdDogbm93KCksIG1lc3NhZ2VzOiByb3cubWVzc2FnZXMuc2xpY2UoMCwgNTApLm1hcCgobWVzc2FnZSkgPT4gKHtcbiAgICAgIGJvZHk6IG1lc3NhZ2UuYm9keSwgaWQ6IG1lc3NhZ2UuX2tleSwgbW9kaWZpZWRBdDogbWVzc2FnZS5tb2RpZmllZCwgcmV2aXNpb246IG1lc3NhZ2UuX3JldlxuICAgIH0pKSxcbiAgICBwcm92ZW5hbmNlOiAnbmF0aXZlX2N1c3RvbWVyX3Zpc2libGVfZmVlZCcsIHJldmlzaW9uLCBzY2hlbWFWZXJzaW9uOiAnc3VwcG9ydF9jb252ZXJzYXRpb25fdjEnLFxuICAgIHRpY2tldElkOiBpbnB1dC50aWNrZXRJZCwgdGlja2V0UmV2aXNpb246IGlucHV0LnRpY2tldFJldmlzaW9uLCB0cnVuY2F0ZWQ6IHJvdy5tZXNzYWdlcy5sZW5ndGggPiA1MCxcbiAgICB2YWxpZFVudGlsOiBNYXRoLm1pbihub3coKSArIDMwMDAwMCwgbGF0ZXN0LmV4cGlyZXMsIHJvdy5jb25zZW50LmV4cGlyZXNBdClcbiAgfTtcbn07XG5leHBvcnQgY29uc3QgcmVhZFN1cHBvcnRDb252ZXJzYXRpb24gPSBhc3luYyAoY29udGV4dDogQXBpQ29udGV4dCwgaW5wdXQ6IFN1cHBvcnRDb252ZXJzYXRpb25JbnB1dCwgbm93ID1cbiAgRGF0ZS5ub3cpID0+IHJlYWRTdXBwb3J0UHVycG9zZUNvbnZlcnNhdGlvbihjb250ZXh0LCBpbnB1dCwgJ3N1cHBvcnRfY29udmVyc2F0aW9uJywgbm93KTtcbmV4cG9ydCBjb25zdCByZWFkU3VwcG9ydFNlbWFudGljU291cmNlID0gYXN5bmMgKGNvbnRleHQ6IEFwaUNvbnRleHQsIGlucHV0OiBTdXBwb3J0Q29udmVyc2F0aW9uSW5wdXQsIG5vdyA9XG4gIERhdGUubm93KSA9PiByZWFkU3VwcG9ydFB1cnBvc2VDb252ZXJzYXRpb24oY29udGV4dCwgaW5wdXQsICdzdXBwb3J0X3NlbWFudGljX3N1bW1hcnknLCBub3cpO1xuIl0sIm5hbWVzIjpbImFxbCIsInNjb3BlQ29udGV4dFRvQXBwIiwidXNlRGIiLCJjcm1DYW5vbmljYWxEaWdlc3QiLCJhdXRob3JpemVDcm1FeGVjdXRpb25HcmFudCIsInN1cHBvcnRDb252ZXJzYXRpb25LZXkiLCJ2YWx1ZSIsInRlc3QiLCJ2YWxpZFN1cHBvcnRDb252ZXJzYXRpb25JbnB1dCIsImlucHV0IiwiQm9vbGVhbiIsIk9iamVjdCIsImtleXMiLCJzb3J0Iiwiam9pbiIsImFwcElkIiwidGlja2V0SWQiLCJ0aWNrZXRSZXZpc2lvbiIsImV2ZXJ5IiwicmVhZFN1cHBvcnRDb252ZXJzYXRpb25Sb3ciLCJkYiIsInB1cnBvc2UiLCJxdWVyeSIsImJpbmQiLCJ0aGVuIiwiY3Vyc29yIiwibmV4dCIsInZhbGlkYXRlU3VwcG9ydENvbnZlcnNhdGlvblJvdyIsInJvdyIsImF0IiwiY29uc2VudCIsInRpY2tldCIsInVzZXIiLCJfa2V5IiwiX3JldiIsImRlbGV0ZWQiLCJpbmNsdWRlcyIsInN0YXR1cyIsInVzZXJJZCIsImFjdGl2ZSIsInNvdXJjZSIsIk51bWJlciIsImlzU2FmZUludGVnZXIiLCJleHBpcmVzQXQiLCJBcnJheSIsImlzQXJyYXkiLCJtZXNzYWdlcyIsInNvbWUiLCJtZXNzYWdlIiwidW5kZWZpbmVkIiwiYmluZGluZ0NvdW50Iiwic3VwcG9ydFRpY2tldElkIiwidmlzaWJpbGl0eSIsImJvZHkiLCJsZW5ndGgiLCJtb2RpZmllZCIsIkVycm9yIiwibWFwIiwic3VwcG9ydFB1cnBvc2VTY29wZSIsInN1cHBvcnRfY29udmVyc2F0aW9uIiwic3VwcG9ydF9lbWFpbF9yZXBseSIsInN1cHBvcnRfc2VtYW50aWNfc3VtbWFyeSIsInJlYWRTdXBwb3J0UHVycG9zZUNvbnZlcnNhdGlvbiIsImNvbnRleHQiLCJub3ciLCJEYXRlIiwiZ3JhbnQiLCJzY29wZWQiLCJkYXRhYmFzZU5hbWUiLCJyZXZpc2lvbiIsImxhdGVzdCIsInNjb3BlIiwiaWQiLCJoYXNoIiwiZ2VuZXJhdGVkQXQiLCJzbGljZSIsIm1vZGlmaWVkQXQiLCJwcm92ZW5hbmNlIiwic2NoZW1hVmVyc2lvbiIsInRydW5jYXRlZCIsInZhbGlkVW50aWwiLCJNYXRoIiwibWluIiwiZXhwaXJlcyIsInJlYWRTdXBwb3J0Q29udmVyc2F0aW9uIiwicmVhZFN1cHBvcnRTZW1hbnRpY1NvdXJjZSJdLCJtYXBwaW5ncyI6IkFBQUEsd0ZBQXdGLEdBQ3hGLFNBQVFBLEdBQUcsUUFBTyxXQUFXO0FBRTdCLFNBQVFDLGlCQUFpQixRQUFPLHlCQUF5QjtBQUN6RCxTQUFRQyxLQUFLLFFBQU8sNEJBQTRCO0FBQ2hELFNBQVFDLGtCQUFrQixRQUFPLDBCQUEwQjtBQUMzRCxTQUFRQywwQkFBMEIsUUFBTyxvQkFBb0I7QUFVN0QsT0FBTyxNQUFNQyx5QkFBeUIsQ0FBQ0MsUUFBb0MsT0FBT0EsVUFBVSxZQUMxRixrQkFBa0JDLElBQUksQ0FBQ0QsT0FBTztBQUNoQyxPQUFPLE1BQU1FLGdDQUFnQyxDQUFDQyxRQUFvQ0MsUUFBUUQsU0FDbkZFLE9BQU9DLElBQUksQ0FBQ0gsT0FBT0ksSUFBSSxHQUFHQyxJQUFJLENBQUMsU0FBUyxtQ0FDeEM7UUFBQ0wsTUFBTU0sS0FBSztRQUFFTixNQUFNTyxRQUFRO1FBQUVQLE1BQU1RLGNBQWM7S0FBQyxDQUFDQyxLQUFLLENBQUNiLHlCQUF5QjtBQWlDMUYsT0FBTyxNQUFNYyw2QkFBNkIsT0FBT0MsSUFBOEJYLE9BQzdFWSxTQUFnQ0MsUUFBUUYsR0FBR0UsS0FBSyxDQUFDQyxJQUFJ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
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ApiContext } from '../types/auth.types.js';
|
|
2
|
+
export interface SupportEmailEventInput {
|
|
3
|
+
appId: string;
|
|
4
|
+
eventId: string;
|
|
5
|
+
kind: 'accepted' | 'delivered' | 'bounce' | 'complaint';
|
|
6
|
+
occurredAt: number;
|
|
7
|
+
providerMessageId: string;
|
|
8
|
+
receiptId: string;
|
|
9
|
+
recipient: string;
|
|
10
|
+
}
|
|
11
|
+
/** Trusted provider bridge only. It must verify SES/SNS authenticity before calling this scoped owner boundary.
|
|
12
|
+
* support_receipt SES tag + exact recipient bind an unknown acknowledgement to its actual submission.
|
|
13
|
+
* Ordinary Ads/customer keys never receive this scope; browser event assertions are not provider evidence.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ingestSupportEmailEvent: (context: ApiContext, input: SupportEmailEventInput, now?: () => number) => Promise<{
|
|
16
|
+
id: string;
|
|
17
|
+
kind: string;
|
|
18
|
+
occurredAt: number;
|
|
19
|
+
receiptId: string;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { aql } from 'arangojs';
|
|
2
|
+
import { useDb } from '../utils/arangodbUtils.js';
|
|
3
|
+
import { crmCanonicalDigest } from './crmCanonicalPolicy.js';
|
|
4
|
+
import { authorizeCrmExecutionGrant, ensureOwnerExecutionCollection } from './crmExecution.js';
|
|
5
|
+
import { supportConversationKey } from './supportConversation.js';
|
|
6
|
+
/** Trusted provider bridge only. It must verify SES/SNS authenticity before calling this scoped owner boundary.
|
|
7
|
+
* support_receipt SES tag + exact recipient bind an unknown acknowledgement to its actual submission.
|
|
8
|
+
* Ordinary Ads/customer keys never receive this scope; browser event assertions are not provider evidence.
|
|
9
|
+
*/ export const ingestSupportEmailEvent = async (context, input, now = Date.now)=>{
|
|
10
|
+
if (!input || Object.keys(input).sort().join(',') !== 'appId,eventId,kind,occurredAt,providerMessageId,receiptId,recipient' || ![
|
|
11
|
+
input.appId,
|
|
12
|
+
input.eventId,
|
|
13
|
+
input.providerMessageId
|
|
14
|
+
].every(supportConversationKey) || !/^[a-f0-9]{64}$/.test(input.receiptId) || typeof input.recipient !== 'string' || input.recipient.length > 254 || ![
|
|
15
|
+
'accepted',
|
|
16
|
+
'delivered',
|
|
17
|
+
'bounce',
|
|
18
|
+
'complaint'
|
|
19
|
+
].includes(input.kind) || !Number.isSafeInteger(input.occurredAt) || input.occurredAt > now() + 60000 || input.occurredAt < now() - 30 * 86400000) {
|
|
20
|
+
throw new Error('invalid_support_email_event');
|
|
21
|
+
}
|
|
22
|
+
const grant = await authorizeCrmExecutionGrant(context, input.appId, 'support:delivery_events', now());
|
|
23
|
+
const db = useDb(grant.databaseName);
|
|
24
|
+
await Promise.all([
|
|
25
|
+
ensureOwnerExecutionCollection(db, 'supportEmailEvents'),
|
|
26
|
+
ensureOwnerExecutionCollection(db, 'hasSupportEmailEvent', true)
|
|
27
|
+
]);
|
|
28
|
+
await db.collection('supportEmailEvents').ensureIndex({
|
|
29
|
+
expireAfter: 0,
|
|
30
|
+
fields: [
|
|
31
|
+
'retireAt'
|
|
32
|
+
],
|
|
33
|
+
type: 'ttl'
|
|
34
|
+
});
|
|
35
|
+
await db.collection('hasSupportEmailEvent').ensureIndex({
|
|
36
|
+
expireAfter: 0,
|
|
37
|
+
fields: [
|
|
38
|
+
'retireAt'
|
|
39
|
+
],
|
|
40
|
+
type: 'ttl'
|
|
41
|
+
});
|
|
42
|
+
const tx = await db.beginTransaction({
|
|
43
|
+
exclusive: [
|
|
44
|
+
'supportEmailEvents',
|
|
45
|
+
'hasSupportEmailEvent',
|
|
46
|
+
'supportEmailReceipts',
|
|
47
|
+
'users'
|
|
48
|
+
],
|
|
49
|
+
read: [
|
|
50
|
+
'supportTickets'
|
|
51
|
+
]
|
|
52
|
+
}, {
|
|
53
|
+
lockTimeout: 10
|
|
54
|
+
});
|
|
55
|
+
const query = (value)=>tx.step(()=>db.query(value));
|
|
56
|
+
try {
|
|
57
|
+
const receipt = await query(aql`RETURN DOCUMENT('supportEmailReceipts', ${input.receiptId})`).then((cursor)=>cursor.next());
|
|
58
|
+
if (!receipt || receipt.appId !== input.appId || receipt.id !== input.receiptId || ![
|
|
59
|
+
'submitting',
|
|
60
|
+
'unknown',
|
|
61
|
+
'accepted'
|
|
62
|
+
].includes(receipt.state) || receipt.recipientHash !== crmCanonicalDigest(input.recipient) || receipt.providerMessageId && receipt.providerMessageId !== input.providerMessageId || input.occurredAt < receipt.createdAt - 60000 || input.occurredAt > receipt.createdAt + 30 * 86400000) {
|
|
63
|
+
throw new Error('support_email_event_submission_hold');
|
|
64
|
+
}
|
|
65
|
+
const id = crmCanonicalDigest([
|
|
66
|
+
input.appId,
|
|
67
|
+
input.eventId
|
|
68
|
+
]);
|
|
69
|
+
const fingerprint = crmCanonicalDigest(input);
|
|
70
|
+
const prior = await query(aql`RETURN DOCUMENT('supportEmailEvents', ${id})`).then((cursor)=>cursor.next());
|
|
71
|
+
if (prior && prior.fingerprint !== fingerprint) {
|
|
72
|
+
throw new Error('support_email_event_conflict');
|
|
73
|
+
}
|
|
74
|
+
const latest = await authorizeCrmExecutionGrant(context, input.appId, 'support:delivery_events', now());
|
|
75
|
+
if (latest.hash !== grant.hash || latest.id !== grant.id || latest.databaseName !== grant.databaseName) {
|
|
76
|
+
throw new Error('support_email_relay_changed');
|
|
77
|
+
}
|
|
78
|
+
if (prior) {
|
|
79
|
+
await tx.commit();
|
|
80
|
+
return prior.receipt;
|
|
81
|
+
}
|
|
82
|
+
const events = await query(aql`FOR event IN supportEmailEvents FILTER event.appId == ${input.appId}
|
|
83
|
+
AND event.receipt.receiptId == ${input.receiptId} LIMIT 32 RETURN event._key`).then((cursor)=>cursor.all());
|
|
84
|
+
if (events.length >= 32) {
|
|
85
|
+
throw new Error('support_email_event_limit');
|
|
86
|
+
}
|
|
87
|
+
// Negative delivery evidence suppresses even when conversation consent/revision has since changed.
|
|
88
|
+
if ([
|
|
89
|
+
'bounce',
|
|
90
|
+
'complaint'
|
|
91
|
+
].includes(input.kind)) {
|
|
92
|
+
await query(aql`LET ticket = DOCUMENT('supportTickets', ${receipt.ticketId})
|
|
93
|
+
FOR user IN users FILTER user._key == ticket.userId AND user.appId == ${input.appId}
|
|
94
|
+
AND user.email == ${input.recipient} AND user.deleted != true
|
|
95
|
+
UPDATE user WITH {emailDeliveryStatus: user.emailDeliveryStatus == 'complaint' OR ${input.kind} == 'complaint'
|
|
96
|
+
? 'complaint' : 'bounce_hold', supportEmailSuppressed: true} IN users`);
|
|
97
|
+
}
|
|
98
|
+
const result = {
|
|
99
|
+
id,
|
|
100
|
+
kind: input.kind,
|
|
101
|
+
occurredAt: input.occurredAt,
|
|
102
|
+
receiptId: input.receiptId
|
|
103
|
+
};
|
|
104
|
+
const retireAt = Math.floor((now() + 30 * 86400000) / 1000);
|
|
105
|
+
await query(aql`INSERT ${{
|
|
106
|
+
_key: id,
|
|
107
|
+
appId: input.appId,
|
|
108
|
+
fingerprint,
|
|
109
|
+
receipt: result,
|
|
110
|
+
retireAt
|
|
111
|
+
}} IN supportEmailEvents`);
|
|
112
|
+
await query(aql`INSERT ${{
|
|
113
|
+
_from: `supportEmailReceipts/${input.receiptId}`,
|
|
114
|
+
_key: id,
|
|
115
|
+
_to: `supportEmailEvents/${id}`,
|
|
116
|
+
appId: input.appId,
|
|
117
|
+
retireAt
|
|
118
|
+
}} IN hasSupportEmailEvent`);
|
|
119
|
+
await query(aql`UPDATE ${input.receiptId} WITH {state: 'accepted', providerAcceptedAt:
|
|
120
|
+
${receipt.providerAcceptedAt || input.occurredAt}, providerMessageId: ${input.providerMessageId}} IN
|
|
121
|
+
supportEmailReceipts`);
|
|
122
|
+
await tx.commit();
|
|
123
|
+
return result;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
await tx.abort();
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi90bXAvcmVhY3RvcmJveC1zdXBwb3J0LW93bmVyLWZyZWV6ZS0yMDI2MTAwOC9pbnB1dC9hY3Rpb25zL3N1cHBvcnRFbWFpbEV2ZW50cy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge2FxbH0gZnJvbSAnYXJhbmdvanMnO1xuXG5pbXBvcnQge3VzZURifSBmcm9tICcuLi91dGlscy9hcmFuZ29kYlV0aWxzLmpzJztcbmltcG9ydCB7Y3JtQ2Fub25pY2FsRGlnZXN0fSBmcm9tICcuL2NybUNhbm9uaWNhbFBvbGljeS5qcyc7XG5pbXBvcnQge2F1dGhvcml6ZUNybUV4ZWN1dGlvbkdyYW50LCBlbnN1cmVPd25lckV4ZWN1dGlvbkNvbGxlY3Rpb259IGZyb20gJy4vY3JtRXhlY3V0aW9uLmpzJztcbmltcG9ydCB7c3VwcG9ydENvbnZlcnNhdGlvbktleX0gZnJvbSAnLi9zdXBwb3J0Q29udmVyc2F0aW9uLmpzJztcblxuaW1wb3J0IHR5cGUge0FwaUNvbnRleHR9IGZyb20gJy4uL3R5cGVzL2F1dGgudHlwZXMuanMnO1xuaW1wb3J0IHR5cGUge1N1cHBvcnRFbWFpbFJlY2VpcHR9IGZyb20gJy4vc3VwcG9ydEVtYWlsSGFuZG9mZi5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3VwcG9ydEVtYWlsRXZlbnRJbnB1dCB7XG4gIGFwcElkOiBzdHJpbmc7XG4gIGV2ZW50SWQ6IHN0cmluZztcbiAga2luZDogJ2FjY2VwdGVkJyB8ICdkZWxpdmVyZWQnIHwgJ2JvdW5jZScgfCAnY29tcGxhaW50JztcbiAgb2NjdXJyZWRBdDogbnVtYmVyO1xuICBwcm92aWRlck1lc3NhZ2VJZDogc3RyaW5nO1xuICByZWNlaXB0SWQ6IHN0cmluZztcbiAgcmVjaXBpZW50OiBzdHJpbmc7XG59XG4vKiogVHJ1c3RlZCBwcm92aWRlciBicmlkZ2Ugb25seS4gSXQgbXVzdCB2ZXJpZnkgU0VTL1NOUyBhdXRoZW50aWNpdHkgYmVmb3JlIGNhbGxpbmcgdGhpcyBzY29wZWQgb3duZXIgYm91bmRhcnkuXG4gKiBzdXBwb3J0X3JlY2VpcHQgU0VTIHRhZyArIGV4YWN0IHJlY2lwaWVudCBiaW5kIGFuIHVua25vd24gYWNrbm93bGVkZ2VtZW50IHRvIGl0cyBhY3R1YWwgc3VibWlzc2lvbi5cbiAqIE9yZGluYXJ5IEFkcy9jdXN0b21lciBrZXlzIG5ldmVyIHJlY2VpdmUgdGhpcyBzY29wZTsgYnJvd3NlciBldmVudCBhc3NlcnRpb25zIGFyZSBub3QgcHJvdmlkZXIgZXZpZGVuY2UuXG4gKi9cbmV4cG9ydCBjb25zdCBpbmdlc3RTdXBwb3J0RW1haWxFdmVudCA9IGFzeW5jIChjb250ZXh0OiBBcGlDb250ZXh0LCBpbnB1dDogU3VwcG9ydEVtYWlsRXZlbnRJbnB1dCwgbm93ID0gRGF0ZS5ub3cpID0+IHtcbiAgaWYoIWlucHV0IHx8IE9iamVjdC5rZXlzKGlucHV0KS5zb3J0KCkuam9pbignLCcpICE9PVxuICAgICdhcHBJZCxldmVudElkLGtpbmQsb2NjdXJyZWRBdCxwcm92aWRlck1lc3NhZ2VJZCxyZWNlaXB0SWQscmVjaXBpZW50J1xuICAgICAgICB8fCAhW2lucHV0LmFwcElkLCBpbnB1dC5ldmVudElkLCBpbnB1dC5wcm92aWRlck1lc3NhZ2VJZF0uZXZlcnkoc3VwcG9ydENvbnZlcnNhdGlvbktleSlcbiAgICAgICAgfHwgIS9eW2EtZjAtOV17NjR9JC8udGVzdChpbnB1dC5yZWNlaXB0SWQpIHx8IHR5cGVvZiBpbnB1dC5yZWNpcGllbnQgIT09ICdzdHJpbmcnIHx8XG4gICAgICAgICAgaW5wdXQucmVjaXBpZW50Lmxlbmd0aCA+IDI1NFxuICAgICAgICB8fCAhWydhY2NlcHRlZCcsICdkZWxpdmVyZWQnLCAnYm91bmNlJywgJ2NvbXBsYWludCddLmluY2x1ZGVzKGlucHV0LmtpbmQpIHx8XG4gICAgICAgICAgIU51bWJlci5pc1NhZmVJbnRlZ2VyKGlucHV0Lm9jY3VycmVkQXQpXG4gICAgICAgIHx8IGlucHV0Lm9jY3VycmVkQXQgPiBub3coKSArIDYwMDAwIHx8IGlucHV0Lm9jY3VycmVkQXQgPCBub3coKSAtICgzMCAqIDg2NDAwMDAwKSkge1xuICAgIHRocm93IG5ldyBFcnJvcignaW52YWxpZF9zdXBwb3J0X2VtYWlsX2V2ZW50Jyk7XG4gIH1cbiAgY29uc3QgZ3JhbnQgPSBhd2FpdCBhdXRob3JpemVDcm1FeGVjdXRpb25HcmFudChjb250ZXh0LCBpbnB1dC5hcHBJZCwgJ3N1cHBvcnQ6ZGVsaXZlcnlfZXZlbnRzJywgbm93KCkpO1xuICBjb25zdCBkYiA9IHVzZURiKGdyYW50LmRhdGFiYXNlTmFtZSk7XG4gIGF3YWl0IFByb21pc2UuYWxsKFtlbnN1cmVPd25lckV4ZWN1dGlvbkNvbGxlY3Rpb24oZGIsICdzdXBwb3J0RW1haWxFdmVudHMnKSxcbiAgICBlbnN1cmVPd25lckV4ZWN1dGlvbkNvbGxlY3Rpb24oZGIsICdoYXNTdXBwb3J0RW1haWxFdmVudCcsIHRydWUpXSk7XG4gIGF3YWl0IGRiLmNvbGxlY3Rpb24oJ3N1cHBvcnRFbWFpbEV2ZW50cycpLmVuc3VyZUluZGV4KHtcbiAgICBleHBpcmVBZnRlcjogMCwgZmllbGRzOiBbJ3JldGlyZUF0J10sIHR5cGU6ICd0dGwnXG4gIH0pO1xuICBhd2FpdCBkYi5jb2xsZWN0aW9uKCdoYXNTdXBwb3J0RW1haWxFdmVudCcpLmVuc3VyZUluZGV4KHtcbiAgICBleHBpcmVBZnRlcjogMCwgZmllbGRzOiBbJ3JldGlyZUF0J10sIHR5cGU6ICd0dGwnXG4gIH0pO1xuICBjb25zdCB0eCA9IGF3YWl0IGRiLmJlZ2luVHJhbnNhY3Rpb24oe1xuICAgIGV4Y2x1c2l2ZTogWydzdXBwb3J0RW1haWxFdmVudHMnLCAnaGFzU3VwcG9ydEVtYWlsRXZlbnQnLCAnc3VwcG9ydEVtYWlsUmVjZWlwdHMnLCAndXNlcnMnXSxcbiAgICByZWFkOiBbJ3N1cHBvcnRUaWNrZXRzJ11cbiAgfSwge1xuICAgIGxvY2tUaW1lb3V0OiAxMFxuICB9KTtcbiAgY29uc3QgcXVlcnkgPSAodmFsdWU6IFJldHVyblR5cGU8dHlwZW9mIGFxbD4pID0+IHR4LnN0ZXAoKCkgPT4gZGIucXVlcnkodmFsdWUpKTtcbiAgdHJ5IHtcbiAgICBjb25zdCByZWNlaXB0ID0gYXdhaXQgcXVlcnkoYXFsYFJFVFVSTiBET0NVTUVOVCgnc3VwcG9ydEVtYWlsUmVjZWlwdHMnLCAke2lucHV0LnJlY2VpcHRJZH0pYCkudGhlbigoY3Vyc29yKSA9PiBjdXJzb3IubmV4dCgpKSBhcyAoU3VwcG9ydEVtYWlsUmVjZWlwdCAmIHtcbiAgICAgIHJlY2lwaWVudEhhc2g6IHN0cmluZztcbiAgICB9KSB8IHVuZGVmaW5lZDtcbiAgICBpZighcmVjZWlwdCB8fCByZWNlaXB0LmFwcElkICE9PSBpbnB1dC5hcHBJZCB8fCByZWNlaXB0LmlkICE9PSBpbnB1dC5yZWNlaXB0SWRcbiAgICAgICAgICAgIHx8ICFbJ3N1Ym1pdHRpbmcnLCAndW5rbm93bicsICdhY2NlcHRlZCddLmluY2x1ZGVzKHJlY2VpcHQuc3RhdGUpXG4gICAgICAgICAgICB8fCByZWNlaXB0LnJlY2lwaWVudEhhc2ggIT09IGNybUNhbm9uaWNhbERpZ2VzdChpbnB1dC5yZWNpcGllbnQpXG4gICAgICAgICAgICB8fCAocmVjZWlwdC5wcm92aWRlck1lc3NhZ2VJZCAmJiByZWNlaXB0LnByb3ZpZGVyTWVzc2FnZUlkICE9PSBpbnB1dC5wcm92aWRlck1lc3NhZ2VJZClcbiAgICAgICAgICAgIHx8IGlucHV0Lm9jY3VycmVkQXQgPCByZWNlaXB0LmNyZWF0ZWRBdCAtIDYwMDAwIHx8IGlucHV0Lm9jY3VycmVkQXQgPiByZWNlaXB0LmNyZWF0ZWRBdCArICgzMCAqIDg2NDAwMDAwKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdzdXBwb3J0X2VtYWlsX2V2ZW50X3N1Ym1pc3Npb25faG9sZCcpO1xuICAgIH1cbiAgICBjb25zdCBpZCA9IGNybUNhbm9uaWNhbERpZ2VzdChbaW5wdXQuYXBwSWQsIGlucHV0LmV2ZW50SWRdKTtcbiAgICBjb25zdCBmaW5nZXJwcmludCA9IGNybUNhbm9uaWNhbERpZ2VzdChpbnB1dCk7XG4gICAgY29uc3QgcHJpb3IgPSBhd2FpdCBxdWVyeShhcWwgYFJFVFVSTiBET0NVTUVOVCgnc3VwcG9ydEVtYWlsRXZlbnRzJywgJHtpZH0pYCkudGhlbigoY3Vyc29yKSA9PiBjdXJzb3IubmV4dCgpKSBhcyB7XG4gICAgICBmaW5nZXJwcmludDogc3RyaW5nO1xuICAgICAgcmVjZWlwdDoge1xuICAgICAgICBpZDogc3RyaW5nO1xuICAgICAgICBraW5kOiBzdHJpbmc7XG4gICAgICAgIG9jY3VycmVkQXQ6IG51bWJlcjtcbiAgICAgICAgcmVjZWlwdElkOiBzdHJpbmc7XG4gICAgICB9O1xuICAgIH0gfCB1bmRlZmluZWQ7XG4gICAgaWYocHJpb3IgJiYgcHJpb3IuZmluZ2VycHJpbnQgIT09IGZpbmdlcnByaW50KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ3N1cHBvcnRfZW1haWxfZXZlbnRfY29uZmxpY3QnKTtcbiAgICB9XG4gICAgY29uc3QgbGF0ZXN0ID0gYXdhaXQgYXV0aG9yaXplQ3JtRXhlY3V0aW9uR3JhbnQoY29udGV4dCwgaW5wdXQuYXBwSWQsICdzdXBwb3J0OmRlbGl2ZXJ5X2V2ZW50cycsIG5vdygpKTtcbiAgICBpZihsYXRlc3QuaGFzaCAhPT0gZ3JhbnQuaGFzaCB8fCBsYXRlc3QuaWQgIT09IGdyYW50LmlkIHx8IGxhdGVzdC5kYXRhYmFzZU5hbWUgIT09IGdyYW50LmRhdGFiYXNlTmFtZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdzdXBwb3J0X2VtYWlsX3JlbGF5X2NoYW5nZWQnKTtcbiAgICB9XG4gICAgaWYocHJpb3IpIHtcbiAgICAgIGF3YWl0IHR4LmNvbW1pdCgpO1xuICAgICAgcmV0dXJuIHByaW9yLnJlY2VpcHQ7XG4gICAgfVxuICAgIGNvbnN0IGV2ZW50cyA9IGF3YWl0IHF1ZXJ5KGFxbCBgRk9SIGV2ZW50IElOIHN1cHBvcnRFbWFpbEV2ZW50cyBGSUxURVIgZXZlbnQuYXBwSWQgPT0gJHtpbnB1dC5hcHBJZH1cbiAgICAgIEFORCBldmVudC5yZWNlaXB0LnJlY2VpcHRJZCA9PSAke2lucHV0LnJlY2VpcHRJZH0gTElNSVQgMzIgUkVUVVJOIGV2ZW50Ll9rZXlgKS50aGVuKChjdXJzb3IpID0+IGN1cnNvci5hbGwoKSk7XG4gICAgaWYoZXZlbnRzLmxlbmd0aCA+PSAzMikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdzdXBwb3J0X2VtYWlsX2V2ZW50X2xpbWl0Jyk7XG4gICAgfVxuICAgIC8vIE5lZ2F0aXZlIGRlbGl2ZXJ5IGV2aWRlbmNlIHN1cHByZXNzZXMgZXZlbiB3aGVuIGNvbnZlcnNhdGlvbiBjb25zZW50L3JldmlzaW9uIGhhcyBzaW5jZSBjaGFuZ2VkLlxuICAgIGlmKFsnYm91bmNlJywgJ2NvbXBsYWludCddLmluY2x1ZGVzKGlucHV0LmtpbmQpKSB7XG4gICAgICBhd2FpdCBxdWVyeShhcWwgYExFVCB0aWNrZXQgPSBET0NVTUVOVCgnc3VwcG9ydFRpY2tldHMnLCAke3JlY2VpcHQudGlja2V0SWR9KVxuICAgICAgICBGT1IgdXNlciBJTiB1c2VycyBGSUxURVIgdXNlci5fa2V5ID09IHRpY2tldC51c2VySWQgQU5EIHVzZXIuYXBwSWQgPT0gJHtpbnB1dC5hcHBJZH1cbiAgICAgICAgQU5EIHVzZXIuZW1haWwgPT0gJHtpbnB1dC5yZWNpcGllbnR9IEFORCB1c2VyLmRlbGV0ZWQgIT0gdHJ1ZVxuICAgICAgICBVUERBVEUgdXNlciBXSVRIIHtlbWFpbERlbGl2ZXJ5U3RhdHVzOiB1c2VyLmVtYWlsRGVsaXZlcnlTdGF0dXMgPT0gJ2NvbXBsYWludCcgT1IgJHtpbnB1dC5raW5kfSA9PSAnY29tcGxhaW50J1xuICAgICAgICAgID8gJ2NvbXBsYWludCcgOiAnYm91bmNlX2hvbGQnLCBzdXBwb3J0RW1haWxTdXBwcmVzc2VkOiB0cnVlfSBJTiB1c2Vyc2ApO1xuICAgIH1cbiAgICBjb25zdCByZXN1bHQgPSB7XG4gICAgICBpZCwga2luZDogaW5wdXQua2luZCwgb2NjdXJyZWRBdDogaW5wdXQub2NjdXJyZWRBdCwgcmVjZWlwdElkOiBpbnB1dC5yZWNlaXB0SWRcbiAgICB9O1xuICAgIGNvbnN0IHJldGlyZUF0ID0gTWF0aC5mbG9vcigobm93KCkgKyAoMzAgKiA4NjQwMDAwMCkpIC8gMTAwMCk7XG4gICAgYXdhaXQgcXVlcnkoYXFsIGBJTlNFUlQgJHt7XG4gICAgICBfa2V5OiBpZCwgYXBwSWQ6IGlucHV0LmFwcElkLCBmaW5nZXJwcmludCwgcmVjZWlwdDogcmVzdWx0LCByZXRpcmVBdFxuICAgIH19IElOIHN1cHBvcnRFbWFpbEV2ZW50c2ApO1xuICAgIGF3YWl0IHF1ZXJ5KGFxbCBgSU5TRVJUICR7e1xuICAgICAgX2Zyb206IGBzdXBwb3J0RW1haWxSZWNlaXB0cy8ke2lucHV0LnJlY2VpcHRJZH1gLCBfa2V5OiBpZCxcbiAgICAgIF90bzogYHN1cHBvcnRFbWFpbEV2ZW50cy8ke2lkfWAsIGFwcElkOiBpbnB1dC5hcHBJZCwgcmV0aXJlQXRcbiAgICB9fSBJTiBoYXNTdXBwb3J0RW1haWxFdmVudGApO1xuICAgIGF3YWl0IHF1ZXJ5KGFxbCBgVVBEQVRFICR7aW5wdXQucmVjZWlwdElkfSBXSVRIIHtzdGF0ZTogJ2FjY2VwdGVkJywgcHJvdmlkZXJBY2NlcHRlZEF0OlxuICAgICAgJHtyZWNlaXB0LnByb3ZpZGVyQWNjZXB0ZWRBdCB8fCBpbnB1dC5vY2N1cnJlZEF0fSwgcHJvdmlkZXJNZXNzYWdlSWQ6ICR7aW5wdXQucHJvdmlkZXJNZXNzYWdlSWR9fSBJTlxuICAgICAgICBzdXBwb3J0RW1haWxSZWNlaXB0c2ApO1xuICAgIGF3YWl0IHR4LmNvbW1pdCgpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH0gY2F0Y2goZXJyb3IpIHtcbiAgICBhd2FpdCB0eC5hYm9ydCgpO1xuICAgIHRocm93IGVycm9yO1xuICB9XG59O1xuIl0sIm5hbWVzIjpbImFxbCIsInVzZURiIiwiY3JtQ2Fub25pY2FsRGlnZXN0IiwiYXV0aG9yaXplQ3JtRXhlY3V0aW9uR3JhbnQiLCJlbnN1cmVPd25lckV4ZWN1dGlvbkNvbGxlY3Rpb24iLCJzdXBwb3J0Q29udmVyc2F0aW9uS2V5IiwiaW5nZXN0U3VwcG9ydEVtYWlsRXZlbnQiLCJjb250ZXh0IiwiaW5wdXQiLCJub3ciLCJEYXRlIiwiT2JqZWN0Iiwia2V5cyIsInNvcnQiLCJqb2luIiwiYXBwSWQiLCJldmVudElkIiwicHJvdmlkZXJNZXNzYWdlSWQiLCJldmVyeSIsInRlc3QiLCJyZWNlaXB0SWQiLCJyZWNpcGllbnQiLCJsZW5ndGgiLCJpbmNsdWRlcyIsImtpbmQiLCJOdW1iZXIiLCJpc1NhZmVJbnRlZ2VyIiwib2NjdXJyZWRBdCIsIkVycm9yIiwiZ3JhbnQiLCJkYiIsImRhdGFiYXNlTmFtZSIsIlByb21pc2UiLCJhbGwiLCJjb2xsZWN0aW9uIiwiZW5zdXJlSW5kZXgiLCJleHBpcmVBZnRlciIsImZpZWxkcyIsInR5cGUiLCJ0eCIsImJlZ2luVHJhbnNhY3Rpb24iLCJleGNsdXNpdmUiLCJyZWFkIiwibG9ja1RpbWVvdXQiLCJxdWVyeSIsInZhbHVlIiwic3RlcCIsInJlY2VpcHQiLCJ0aGVuIiwiY3Vyc29yIiwibmV4dCIsImlkIiwic3RhdGUiLCJyZWNpcGllbnRIYXNoIiwiY3JlYXRlZEF0IiwiZmluZ2VycHJpbnQiLCJwcmlvciIsImxhdGVzdCIsImhhc2giLCJjb21taXQiLCJldmVudHMiLCJ0aWNrZXRJZCIsInJlc3VsdCIsInJldGlyZUF0IiwiTWF0aCIsImZsb29yIiwiX2tleSIsIl9mcm9tIiwiX3RvIiwicHJvdmlkZXJBY2NlcHRlZEF0IiwiZXJyb3IiLCJhYm9ydCJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBUUEsR0FBRyxRQUFPLFdBQVc7QUFFN0IsU0FBUUMsS0FBSyxRQUFPLDRCQUE0QjtBQUNoRCxTQUFRQyxrQkFBa0IsUUFBTywwQkFBMEI7QUFDM0QsU0FBUUMsMEJBQTBCLEVBQUVDLDhCQUE4QixRQUFPLG9CQUFvQjtBQUM3RixTQUFRQyxzQkFBc0IsUUFBTywyQkFBMkI7QUFjaEU7OztDQUdDLEdBQ0QsT0FBTyxNQUFNQywwQkFBMEIsT0FB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
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { sesSend } from './email.js';
|
|
2
|
+
import type { ApiContext } from '../types/auth.types.js';
|
|
3
|
+
import type { SupportConversationInput } from './supportConversation.js';
|
|
4
|
+
export interface SupportEmailPreviewInput extends SupportConversationInput {
|
|
5
|
+
body: string;
|
|
6
|
+
expectedConversationRevision: string;
|
|
7
|
+
idempotencyKey: string;
|
|
8
|
+
subject: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SupportEmailReceipt {
|
|
11
|
+
appId: string;
|
|
12
|
+
contentStatus: 'current' | 'redacted';
|
|
13
|
+
conversationRevision: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
id: string;
|
|
17
|
+
maxActions: 1;
|
|
18
|
+
providerAcceptedAt?: number;
|
|
19
|
+
providerMessageId?: string;
|
|
20
|
+
revision: string;
|
|
21
|
+
state: 'pending' | 'approved' | 'rejected' | 'submitting' | 'unknown' | 'accepted';
|
|
22
|
+
ticketId: string;
|
|
23
|
+
ticketRevision: string;
|
|
24
|
+
}
|
|
25
|
+
/** Current exact email target review; no inferred channel binding or inbound provider provenance. */
|
|
26
|
+
export declare const readSupportEmailHandoffTarget: (context: ApiContext, input: SupportConversationInput, now?: () => number) => Promise<{
|
|
27
|
+
appId: string;
|
|
28
|
+
conversationRevision: string;
|
|
29
|
+
recipient: string;
|
|
30
|
+
sender: string;
|
|
31
|
+
ticketId: string;
|
|
32
|
+
ticketRevision: string;
|
|
33
|
+
validUntil: number;
|
|
34
|
+
}>;
|
|
35
|
+
/** Own bounded conversation revision plus a separately consented exact email recipient/body. */
|
|
36
|
+
export declare const previewSupportEmailHandoff: (context: ApiContext, input: SupportEmailPreviewInput, now?: () => number) => Promise<{
|
|
37
|
+
appId: string;
|
|
38
|
+
contentStatus: 'current' | 'redacted';
|
|
39
|
+
conversationRevision: string;
|
|
40
|
+
createdAt: number;
|
|
41
|
+
expiresAt: number;
|
|
42
|
+
id: string;
|
|
43
|
+
maxActions: 1;
|
|
44
|
+
providerAcceptedAt?: number;
|
|
45
|
+
providerMessageId?: string;
|
|
46
|
+
revision: string;
|
|
47
|
+
state: 'pending' | 'approved' | 'rejected' | 'submitting' | 'unknown' | 'accepted';
|
|
48
|
+
ticketId: string;
|
|
49
|
+
ticketRevision: string;
|
|
50
|
+
body?: string;
|
|
51
|
+
recipient?: string;
|
|
52
|
+
sender?: string;
|
|
53
|
+
subject?: string;
|
|
54
|
+
}>;
|
|
55
|
+
/** Inspection redacts private content immediately when any current source/authority fails. */
|
|
56
|
+
export declare const getSupportEmailHandoff: (context: ApiContext, appId: string, id: string, now?: () => number) => Promise<{
|
|
57
|
+
appId: string;
|
|
58
|
+
contentStatus: 'current' | 'redacted';
|
|
59
|
+
conversationRevision: string;
|
|
60
|
+
createdAt: number;
|
|
61
|
+
expiresAt: number;
|
|
62
|
+
id: string;
|
|
63
|
+
maxActions: 1;
|
|
64
|
+
providerAcceptedAt?: number;
|
|
65
|
+
providerMessageId?: string;
|
|
66
|
+
revision: string;
|
|
67
|
+
state: 'pending' | 'approved' | 'rejected' | 'submitting' | 'unknown' | 'accepted';
|
|
68
|
+
ticketId: string;
|
|
69
|
+
ticketRevision: string;
|
|
70
|
+
body?: string;
|
|
71
|
+
recipient?: string;
|
|
72
|
+
sender?: string;
|
|
73
|
+
subject?: string;
|
|
74
|
+
}>;
|
|
75
|
+
/** Separate explicit revision-bound approval/rejection; never granted by an Ads plan. */
|
|
76
|
+
export declare const decideSupportEmailHandoff: (context: ApiContext, appId: string, id: string, revision: string, decision: 'approved' | 'rejected', now?: () => number) => Promise<{
|
|
77
|
+
appId: string;
|
|
78
|
+
contentStatus: 'current' | 'redacted';
|
|
79
|
+
conversationRevision: string;
|
|
80
|
+
createdAt: number;
|
|
81
|
+
expiresAt: number;
|
|
82
|
+
id: string;
|
|
83
|
+
maxActions: 1;
|
|
84
|
+
providerAcceptedAt?: number;
|
|
85
|
+
providerMessageId?: string;
|
|
86
|
+
revision: string;
|
|
87
|
+
state: 'pending' | 'approved' | 'rejected' | 'submitting' | 'unknown' | 'accepted';
|
|
88
|
+
ticketId: string;
|
|
89
|
+
ticketRevision: string;
|
|
90
|
+
body?: string;
|
|
91
|
+
recipient?: string;
|
|
92
|
+
sender?: string;
|
|
93
|
+
subject?: string;
|
|
94
|
+
}>;
|
|
95
|
+
/** Durable claim precedes exactly one SES attempt. Missing/ambiguous acknowledgement is a permanent hold. */
|
|
96
|
+
export declare const executeSupportEmailHandoff: (context: ApiContext, appId: string, id: string, revision: string, now?: () => number, send?: typeof sesSend) => Promise<{
|
|
97
|
+
appId: string;
|
|
98
|
+
contentStatus: 'current' | 'redacted';
|
|
99
|
+
conversationRevision: string;
|
|
100
|
+
createdAt: number;
|
|
101
|
+
expiresAt: number;
|
|
102
|
+
id: string;
|
|
103
|
+
maxActions: 1;
|
|
104
|
+
providerAcceptedAt?: number;
|
|
105
|
+
providerMessageId?: string;
|
|
106
|
+
revision: string;
|
|
107
|
+
state: 'pending' | 'approved' | 'rejected' | 'submitting' | 'unknown' | 'accepted';
|
|
108
|
+
ticketId: string;
|
|
109
|
+
ticketRevision: string;
|
|
110
|
+
body?: string;
|
|
111
|
+
recipient?: string;
|
|
112
|
+
sender?: string;
|
|
113
|
+
subject?: string;
|
|
114
|
+
}>;
|