@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,24 @@
|
|
|
1
|
+
import type { ApiContext } from '../types/auth.types.js';
|
|
2
|
+
export interface CrmDeliveryEventInput {
|
|
3
|
+
appId: string;
|
|
4
|
+
bounceType?: 'Permanent' | 'Transient' | 'Undetermined';
|
|
5
|
+
eventId: string;
|
|
6
|
+
kind: 'delivered' | 'bounce' | 'complaint';
|
|
7
|
+
occurredAt: number;
|
|
8
|
+
providerMessageId: string;
|
|
9
|
+
receiptId: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CrmDeliveryEventReceipt {
|
|
12
|
+
bounceType?: CrmDeliveryEventInput['bounceType'];
|
|
13
|
+
id: string;
|
|
14
|
+
kind: CrmDeliveryEventInput['kind'];
|
|
15
|
+
occurredAt: number;
|
|
16
|
+
receiptId: string;
|
|
17
|
+
suppressed: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Trusted server relay scope authenticates a deployed relay; it does not verify unsigned SNS claims. */
|
|
20
|
+
export declare const ingestCrmDeliveryEvent: (context: ApiContext, input: CrmDeliveryEventInput, now?: () => number) => Promise<CrmDeliveryEventReceipt>;
|
|
21
|
+
/** Native administrator privacy action; persistent minimized tombstone prevents prior accepted-message reingestion. */
|
|
22
|
+
export declare const eraseCrmDeliveryEvidence: (context: ApiContext, appId: string, userId: string, now?: () => number) => Promise<{
|
|
23
|
+
erased: number;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop, no-underscore-dangle -- Sequential collection setup and native graph metadata. */ import { aql } from 'arangojs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { scopeContextToApp } from '../utils/appContext.js';
|
|
4
|
+
import { useDb } from '../utils/arangodbUtils.js';
|
|
5
|
+
import { requireCrmAdministrator } from './crm.js';
|
|
6
|
+
import { crmDeliveryPrivacyKey } from './crmDeliveryEvidence.js';
|
|
7
|
+
import { authorizeCrmExecutionGrant, ensureOwnerExecutionCollection } from './crmExecution.js';
|
|
8
|
+
const digest = (value)=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
9
|
+
const identifier = (value)=>typeof value === 'string' && /^[\w.-]{1,200}$/.test(value);
|
|
10
|
+
const valid = (input, at)=>Boolean(input && identifier(input.appId) && input.appId.length <= 100 && identifier(input.eventId) && /^[a-f0-9]{64}$/.test(input.receiptId) && identifier(input.providerMessageId) && [
|
|
11
|
+
'delivered',
|
|
12
|
+
'bounce',
|
|
13
|
+
'complaint'
|
|
14
|
+
].includes(input.kind) && Object.keys(input).every((key)=>[
|
|
15
|
+
'appId',
|
|
16
|
+
'bounceType',
|
|
17
|
+
'eventId',
|
|
18
|
+
'kind',
|
|
19
|
+
'occurredAt',
|
|
20
|
+
'providerMessageId',
|
|
21
|
+
'receiptId'
|
|
22
|
+
].includes(key)) && (input.kind === 'bounce' ? [
|
|
23
|
+
'Permanent',
|
|
24
|
+
'Transient',
|
|
25
|
+
'Undetermined'
|
|
26
|
+
].includes(input.bounceType || '') : input.bounceType === undefined) && Number.isSafeInteger(input.occurredAt) && input.occurredAt <= at + 60000 && input.occurredAt >= at - 30 * 86400000);
|
|
27
|
+
/** Trusted server relay scope authenticates a deployed relay; it does not verify unsigned SNS claims. */ export const ingestCrmDeliveryEvent = async (context, input, now = Date.now)=>{
|
|
28
|
+
if (!valid(input, now())) {
|
|
29
|
+
throw new Error('invalid_crm_delivery_event');
|
|
30
|
+
}
|
|
31
|
+
const grant = await authorizeCrmExecutionGrant(context, input.appId, 'crm:delivery_events', now());
|
|
32
|
+
const db = useDb(grant.databaseName);
|
|
33
|
+
const documents = [
|
|
34
|
+
'crmDeliveryEvents',
|
|
35
|
+
'crmExecutionReceipts',
|
|
36
|
+
'crmDeliveryPrivacy',
|
|
37
|
+
'users'
|
|
38
|
+
];
|
|
39
|
+
const edges = [
|
|
40
|
+
'hasCrmExecutionDelivery',
|
|
41
|
+
'hasUserCrmDelivery'
|
|
42
|
+
];
|
|
43
|
+
for (const name of [
|
|
44
|
+
...documents,
|
|
45
|
+
...edges
|
|
46
|
+
]){
|
|
47
|
+
await ensureOwnerExecutionCollection(db, name, edges.includes(name));
|
|
48
|
+
}
|
|
49
|
+
const tx = await db.beginTransaction({
|
|
50
|
+
exclusive: [
|
|
51
|
+
...documents,
|
|
52
|
+
...edges
|
|
53
|
+
]
|
|
54
|
+
}, {
|
|
55
|
+
lockTimeout: 10
|
|
56
|
+
});
|
|
57
|
+
const query = (value)=>tx.step(()=>db.query(value));
|
|
58
|
+
try {
|
|
59
|
+
const receipt = await query(aql`RETURN DOCUMENT('crmExecutionReceipts', ${input.receiptId})`).then((cursor)=>cursor.next());
|
|
60
|
+
if (!receipt || receipt.appId !== input.appId || receipt.id !== input.receiptId || receipt.state !== 'applied' || ![
|
|
61
|
+
'crm_outreach',
|
|
62
|
+
'scheduling_reminder'
|
|
63
|
+
].includes(receipt.kind) || receipt.providerMessageId !== input.providerMessageId || !Number.isSafeInteger(receipt.providerAcceptedAt) || receipt.providerAcceptedAt < receipt.createdAt || receipt.providerAcceptedAt > now() || input.occurredAt < receipt.providerAcceptedAt - 60000 || input.occurredAt > receipt.providerAcceptedAt + 30 * 86400000) {
|
|
64
|
+
throw new Error('crm_delivery_accepted_message_required');
|
|
65
|
+
}
|
|
66
|
+
const user = await query(aql`RETURN DOCUMENT('users', ${receipt.recipient.id})`).then((cursor)=>cursor.next());
|
|
67
|
+
if (!user || user.deleted === true || user.email !== receipt.recipient.email || user._key !== receipt.recipient.id) {
|
|
68
|
+
throw new Error('crm_delivery_recipient_changed');
|
|
69
|
+
}
|
|
70
|
+
const erased = await query(aql`RETURN DOCUMENT('crmDeliveryPrivacy', ${crmDeliveryPrivacyKey(input.appId, user._key)})`).then((cursor)=>cursor.next());
|
|
71
|
+
if (erased && receipt.providerAcceptedAt <= erased.erasedAt) {
|
|
72
|
+
throw new Error('crm_delivery_privacy_erased');
|
|
73
|
+
}
|
|
74
|
+
const id = digest([
|
|
75
|
+
input.appId,
|
|
76
|
+
input.eventId
|
|
77
|
+
]);
|
|
78
|
+
const fingerprint = digest([
|
|
79
|
+
input.appId,
|
|
80
|
+
input.receiptId,
|
|
81
|
+
input.providerMessageId,
|
|
82
|
+
input.kind,
|
|
83
|
+
input.bounceType,
|
|
84
|
+
input.occurredAt
|
|
85
|
+
]);
|
|
86
|
+
const previous = await query(aql`RETURN DOCUMENT('crmDeliveryEvents', ${id})`).then((cursor)=>cursor.next());
|
|
87
|
+
if (previous && previous.fingerprint !== fingerprint) {
|
|
88
|
+
throw new Error('crm_delivery_event_conflict');
|
|
89
|
+
}
|
|
90
|
+
const current = await authorizeCrmExecutionGrant(context, input.appId, 'crm:delivery_events', now());
|
|
91
|
+
if (current.id !== grant.id || current.hash !== grant.hash || current.databaseName !== grant.databaseName) {
|
|
92
|
+
throw new Error('crm_delivery_relay_changed');
|
|
93
|
+
}
|
|
94
|
+
const suppressed = input.kind !== 'delivered' || Boolean(user.emailDeliveryStatus && user.emailDeliveryStatus !== 'valid');
|
|
95
|
+
if (previous) {
|
|
96
|
+
await tx.commit();
|
|
97
|
+
return {
|
|
98
|
+
...previous.receipt,
|
|
99
|
+
suppressed
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const timeline = await query(aql`FOR event IN crmDeliveryEvents
|
|
103
|
+
FILTER event.appId == ${input.appId} AND event.receipt.receiptId == ${input.receiptId}
|
|
104
|
+
LIMIT 32 RETURN event._key`).then((cursor)=>cursor.all());
|
|
105
|
+
if (timeline.length >= 32) {
|
|
106
|
+
throw new Error('crm_delivery_timeline_limit');
|
|
107
|
+
}
|
|
108
|
+
const result = {
|
|
109
|
+
bounceType: input.bounceType,
|
|
110
|
+
id,
|
|
111
|
+
kind: input.kind,
|
|
112
|
+
occurredAt: input.occurredAt,
|
|
113
|
+
receiptId: input.receiptId,
|
|
114
|
+
suppressed
|
|
115
|
+
};
|
|
116
|
+
await query(aql`INSERT ${{
|
|
117
|
+
_key: id,
|
|
118
|
+
appId: input.appId,
|
|
119
|
+
fingerprint,
|
|
120
|
+
receipt: result,
|
|
121
|
+
receivedAt: now(),
|
|
122
|
+
relayGrantId: grant.id
|
|
123
|
+
}} INTO crmDeliveryEvents`);
|
|
124
|
+
if (input.kind !== 'delivered') {
|
|
125
|
+
// Any bounce holds sends; classification is evidence, not a claim that a transient bounce is permanent.
|
|
126
|
+
const status = input.kind === 'complaint' || user.emailDeliveryStatus === 'complaint' ? 'complaint' : 'bounce_hold';
|
|
127
|
+
await query(aql`UPDATE ${user._key} WITH {emailDeliveryStatus: ${status}, modified: ${now()}} IN users`);
|
|
128
|
+
}
|
|
129
|
+
await query(aql`INSERT ${{
|
|
130
|
+
_from: `crmExecutionReceipts/${input.receiptId}`,
|
|
131
|
+
_key: id,
|
|
132
|
+
_to: `crmDeliveryEvents/${id}`,
|
|
133
|
+
appId: input.appId
|
|
134
|
+
}} INTO hasCrmExecutionDelivery`);
|
|
135
|
+
await query(aql`INSERT ${{
|
|
136
|
+
_from: `users/${user._key}`,
|
|
137
|
+
_key: id,
|
|
138
|
+
_to: `crmDeliveryEvents/${id}`,
|
|
139
|
+
appId: input.appId
|
|
140
|
+
}} INTO hasUserCrmDelivery`);
|
|
141
|
+
const closing = await authorizeCrmExecutionGrant(context, input.appId, 'crm:delivery_events', now());
|
|
142
|
+
if (closing.id !== grant.id || closing.hash !== grant.hash || closing.databaseName !== grant.databaseName) {
|
|
143
|
+
throw new Error('crm_delivery_relay_changed');
|
|
144
|
+
}
|
|
145
|
+
await tx.commit();
|
|
146
|
+
return result;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
await tx.abort();
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
/** Native administrator privacy action; persistent minimized tombstone prevents prior accepted-message reingestion. */ export const eraseCrmDeliveryEvidence = async (context, appId, userId, now = Date.now)=>{
|
|
153
|
+
requireCrmAdministrator(context);
|
|
154
|
+
if (!identifier(appId) || appId.length > 100 || !identifier(userId) || userId.length > 100) {
|
|
155
|
+
throw new Error('invalid_crm_delivery_erasure');
|
|
156
|
+
}
|
|
157
|
+
const scoped = await scopeContextToApp(context, appId);
|
|
158
|
+
const db = useDb(scoped.databaseName);
|
|
159
|
+
const documents = [
|
|
160
|
+
'crmDeliveryEvents',
|
|
161
|
+
'crmDeliveryPrivacy'
|
|
162
|
+
];
|
|
163
|
+
const edges = [
|
|
164
|
+
'hasCrmExecutionDelivery',
|
|
165
|
+
'hasUserCrmDelivery'
|
|
166
|
+
];
|
|
167
|
+
for (const name of [
|
|
168
|
+
...documents,
|
|
169
|
+
...edges
|
|
170
|
+
]){
|
|
171
|
+
await ensureOwnerExecutionCollection(db, name, edges.includes(name));
|
|
172
|
+
}
|
|
173
|
+
const tx = await db.beginTransaction({
|
|
174
|
+
exclusive: [
|
|
175
|
+
...documents,
|
|
176
|
+
...edges
|
|
177
|
+
]
|
|
178
|
+
}, {
|
|
179
|
+
lockTimeout: 10
|
|
180
|
+
});
|
|
181
|
+
const query = (value)=>tx.step(()=>db.query(value));
|
|
182
|
+
try {
|
|
183
|
+
const ids = await query(aql`FOR edge IN hasUserCrmDelivery
|
|
184
|
+
FILTER edge.appId == ${appId} AND edge._from == ${`users/${userId}`}
|
|
185
|
+
RETURN PARSE_IDENTIFIER(edge._to).key`).then((cursor)=>cursor.all());
|
|
186
|
+
const id = crmDeliveryPrivacyKey(appId, userId);
|
|
187
|
+
await query(aql`UPSERT {_key: ${id}} INSERT ${{
|
|
188
|
+
_key: id,
|
|
189
|
+
erasedAt: now()
|
|
190
|
+
}}
|
|
191
|
+
UPDATE {erasedAt: MAX([OLD.erasedAt, ${now()}])} IN crmDeliveryPrivacy`);
|
|
192
|
+
await query(aql`FOR event IN crmDeliveryEvents FILTER event._key IN ${ids} REMOVE event IN crmDeliveryEvents`);
|
|
193
|
+
await query(aql`FOR edge IN hasCrmExecutionDelivery FILTER edge.appId == ${appId}
|
|
194
|
+
AND PARSE_IDENTIFIER(edge._to).key IN ${ids} REMOVE edge IN hasCrmExecutionDelivery`);
|
|
195
|
+
await query(aql`FOR edge IN hasUserCrmDelivery FILTER edge.appId == ${appId}
|
|
196
|
+
AND edge._from == ${`users/${userId}`} REMOVE edge IN hasUserCrmDelivery`);
|
|
197
|
+
await tx.commit();
|
|
198
|
+
return {
|
|
199
|
+
erased: ids.length
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
await tx.abort();
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9uaXRyb2c3L0RldmVsb3BtZW50L3JlYWt0b3IvcGFja2FnZXMvcmVha3Rvci9zcmMvYWN0aW9ucy9jcm1EZWxpdmVyeUV2ZW50cy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKiBlc2xpbnQtZGlzYWJsZSBuby1hd2FpdC1pbi1sb29wLCBuby11bmRlcnNjb3JlLWRhbmdsZSAtLSBTZXF1ZW50aWFsIGNvbGxlY3Rpb24gc2V0dXAgYW5kIG5hdGl2ZSBncmFwaCBtZXRhZGF0YS4gKi9cbmltcG9ydCB7YXFsfSBmcm9tICdhcmFuZ29qcyc7XG5pbXBvcnQge2NyZWF0ZUhhc2h9IGZyb20gJ25vZGU6Y3J5cHRvJztcblxuaW1wb3J0IHtzY29wZUNvbnRleHRUb0FwcH0gZnJvbSAnLi4vdXRpbHMvYXBwQ29udGV4dC5qcyc7XG5pbXBvcnQge3VzZURifSBmcm9tICcuLi91dGlscy9hcmFuZ29kYlV0aWxzLmpzJztcbmltcG9ydCB7cmVxdWlyZUNybUFkbWluaXN0cmF0b3J9IGZyb20gJy4vY3JtLmpzJztcbmltcG9ydCB7Y3JtRGVsaXZlcnlQcml2YWN5S2V5fSBmcm9tICcuL2NybURlbGl2ZXJ5RXZpZGVuY2UuanMnO1xuaW1wb3J0IHthdXRob3JpemVDcm1FeGVjdXRpb25HcmFudCwgZW5zdXJlT3duZXJFeGVjdXRpb25Db2xsZWN0aW9ufSBmcm9tICcuL2NybUV4ZWN1dGlvbi5qcyc7XG5cbmltcG9ydCB0eXBlIHtBcGlDb250ZXh0fSBmcm9tICcuLi90eXBlcy9hdXRoLnR5cGVzLmpzJztcbmltcG9ydCB0eXBlIHtDcm1FeGVjdXRpb25SZWNlaXB0fSBmcm9tICcuL2NybUV4ZWN1dGlvbi5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ3JtRGVsaXZlcnlFdmVudElucHV0IHtcbiAgYXBwSWQ6IHN0cmluZztcbiAgYm91bmNlVHlwZT86ICdQZXJtYW5lbnQnIHwgJ1RyYW5zaWVudCcgfCAnVW5kZXRlcm1pbmVkJztcbiAgZXZlbnRJZDogc3RyaW5nO1xuICBraW5kOiAnZGVsaXZlcmVkJyB8ICdib3VuY2UnIHwgJ2NvbXBsYWludCc7XG4gIG9jY3VycmVkQXQ6IG51bWJlcjtcbiAgcHJvdmlkZXJNZXNzYWdlSWQ6IHN0cmluZztcbiAgcmVjZWlwdElkOiBzdHJpbmc7XG59XG5leHBvcnQgaW50ZXJmYWNlIENybURlbGl2ZXJ5RXZlbnRSZWNlaXB0IHtcbiAgYm91bmNlVHlwZT86IENybURlbGl2ZXJ5RXZlbnRJbnB1dFsnYm91bmNlVHlwZSddO1xuICBpZDogc3RyaW5nO1xuICBraW5kOiBDcm1EZWxpdmVyeUV2ZW50SW5wdXRbJ2tpbmQnXTtcbiAgb2NjdXJyZWRBdDogbnVtYmVyO1xuICByZWNlaXB0SWQ6IHN0cmluZztcbiAgc3VwcHJlc3NlZDogYm9vbGVhbjtcbn1cbmNvbnN0IGRpZ2VzdCA9ICh2YWx1ZTogdW5rbm93bik6IHN0cmluZyA9PiBjcmVhdGVIYXNoKCdzaGEyNTYnKS51cGRhdGUoSlNPTi5zdHJpbmdpZnkodmFsdWUpKS5kaWdlc3QoJ2hleCcpO1xuY29uc3QgaWRlbnRpZmllciA9ICh2YWx1ZTogdW5rbm93bik6IHZhbHVlIGlzIHN0cmluZyA9PiB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnICYmIC9eW1xcdy4tXXsxLDIwMH0kLy50ZXN0KHZhbHVlKTtcbmNvbnN0IHZhbGlkID0gKGlucHV0OiBDcm1EZWxpdmVyeUV2ZW50SW5wdXQsIGF0OiBudW1iZXIpOiBib29sZWFuID0+IEJvb2xlYW4oaW5wdXQgJiYgaWRlbnRpZmllcihpbnB1dC5hcHBJZClcbiAgJiYgaW5wdXQuYXBwSWQubGVuZ3RoIDw9IDEwMCAmJiBpZGVudGlmaWVyKGlucHV0LmV2ZW50SWQpICYmIC9eW2EtZjAtOV17NjR9JC8udGVzdChpbnB1dC5yZWNlaXB0SWQpXG4gICYmIGlkZW50aWZpZXIoaW5wdXQucHJvdmlkZXJNZXNzYWdlSWQpICYmIFsnZGVsaXZlcmVkJywgJ2JvdW5jZScsICdjb21wbGFpbnQnXS5pbmNsdWRlcyhpbnB1dC5raW5kKVxuICAmJiBPYmplY3Qua2V5cyhpbnB1dCkuZXZlcnkoKGtleSkgPT4gWydhcHBJZCcsICdib3VuY2VUeXBlJywgJ2V2ZW50SWQnLCAna2luZCcsICdvY2N1cnJlZEF0JywgJ3Byb3ZpZGVyTWVzc2FnZUlkJywgJ3JlY2VpcHRJZCddLmluY2x1ZGVzKGtleSkpXG4gICYmIChpbnB1dC5raW5kID09PSAnYm91bmNlJyA/IFsnUGVybWFuZW50JywgJ1RyYW5zaWVudCcsICdVbmRldGVybWluZWQnXS5pbmNsdWRlcyhpbnB1dC5ib3VuY2VUeXBlIHx8ICcnKSA6IGlucHV0LmJvdW5jZVR5cGUgPT09IHVuZGVmaW5lZClcbiAgJiYgTnVtYmVyLmlzU2FmZUludGVnZXIoaW5wdXQub2NjdXJyZWRBdCkgJiYgaW5wdXQub2NjdXJyZWRBdCA8PSBhdCArIDYwMDAwXG4gICYmIGlucHV0Lm9jY3VycmVkQXQgPj0gYXQgLSAoMzAgKiA4NjQwMDAwMCkpO1xuXG4vKiogVHJ1c3RlZCBzZXJ2ZXIgcmVsYXkgc2NvcGUgYXV0aGVudGljYXRlcyBhIGRlcGxveWVkIHJlbGF5OyBpdCBkb2VzIG5vdCB2ZXJpZnkgdW5zaWduZWQgU05TIGNsYWltcy4gKi9cbmV4cG9ydCBjb25zdCBpbmdlc3RDcm1EZWxpdmVyeUV2ZW50ID0gYXN5bmMgKFxuICBjb250ZXh0OiBBcGlDb250ZXh0LCBpbnB1dDogQ3JtRGVsaXZlcnlFdmVudElucHV0LCBub3cgPSBEYXRlLm5vd1xuKTogUHJvbWlzZTxDcm1EZWxpdmVyeUV2ZW50UmVjZWlwdD4gPT4ge1xuICBpZighdmFsaWQoaW5wdXQsIG5vdygpKSkge1xuICAgIHRocm93IG5ldyBFcnJvcignaW52YWxpZF9jcm1fZGVsaXZlcnlfZXZlbnQnKTtcbiAgfVxuICBjb25zdCBncmFudCA9IGF3YWl0IGF1dGhvcml6ZUNybUV4ZWN1dGlvbkdyYW50KGNvbnRleHQsIGlucHV0LmFwcElkLCAnY3JtOmRlbGl2ZXJ5X2V2ZW50cycsIG5vdygpKTtcbiAgY29uc3QgZGIgPSB1c2VEYihncmFudC5kYXRhYmFzZU5hbWUpO1xuICBjb25zdCBkb2N1bWVudHMgPSBbJ2NybURlbGl2ZXJ5RXZlbnRzJywgJ2NybUV4ZWN1dGlvblJlY2VpcHRzJywgJ2NybURlbGl2ZXJ5UHJpdmFjeScsICd1c2VycyddO1xuICBjb25zdCBlZGdlcyA9IFsnaGFzQ3JtRXhlY3V0aW9uRGVsaXZlcnknLCAnaGFzVXNlckNybURlbGl2ZXJ5J107XG4gIGZvcihjb25zdCBuYW1lIG9mIFsuLi5kb2N1bWVudHMsIC4uLmVkZ2VzXSkge1xuICAgIGF3YWl0IGVuc3VyZU93bmVyRXhlY3V0aW9uQ29sbGVjdGlvbihkYiwgbmFtZSwgZWRnZXMuaW5jbHVkZXMobmFtZSkpO1xuICB9XG4gIGNvbnN0IHR4ID0gYXdhaXQgZGIuYmVnaW5UcmFuc2FjdGlvbih7ZXhjbHVzaXZlOiBbLi4uZG9jdW1lbnRzLCAuLi5lZGdlc119LCB7bG9ja1RpbWVvdXQ6IDEwfSk7XG4gIGNvbnN0IHF1ZXJ5ID0gKHZhbHVlOiBSZXR1cm5UeXBlPHR5cGVvZiBhcWw+KSA9PiB0eC5zdGVwKCgpID0+IGRiLnF1ZXJ5KHZhbHVlKSk7XG4gIHRyeSB7XG4gICAgY29uc3QgcmVjZWlwdCA9IGF3YWl0IHF1ZXJ5KGFxbGBSRVRVUk4gRE9DVU1FTlQoJ2NybUV4ZWN1dGlvblJlY2VpcHRzJywgJHtpbnB1dC5yZWNlaXB0SWR9KWApXG4gICAgICAudGhlbigoY3Vyc29yKSA9PiBjdXJzb3IubmV4dCgpKSBhcyBDcm1FeGVjdXRpb25SZWNlaXB0O1xuICAgIGlmKCFyZWNlaXB0IHx8IHJlY2VpcHQuYXBwSWQgIT09IGlucHV0LmFwcElkIHx8IHJlY2VpcHQuaWQgIT09IGlucHV0LnJlY2VpcHRJZCB8fCByZWNlaXB0LnN0YXRlICE9PSAnYXBwbGllZCdcbiAgICAgIHx8ICFbJ2NybV9vdXRyZWFjaCcsICdzY2hlZHVsaW5nX3JlbWluZGVyJ10uaW5jbHVkZXMocmVjZWlwdC5raW5kKSB8fCByZWNlaXB0LnByb3ZpZGVyTWVzc2FnZUlkICE9PSBpbnB1dC5wcm92aWRlck1lc3NhZ2VJZFxuICAgICAgfHwgIU51bWJlci5pc1NhZmVJbnRlZ2VyKHJlY2VpcHQucHJvdmlkZXJBY2NlcHRlZEF0KSB8fCByZWNlaXB0LnByb3ZpZGVyQWNjZXB0ZWRBdCEgPCByZWNlaXB0LmNyZWF0ZWRBdFxuICAgICAgfHwgcmVjZWlwdC5wcm92aWRlckFjY2VwdGVkQXQhID4gbm93KCkgfHwgaW5wdXQub2NjdXJyZWRBdCA8IHJlY2VpcHQucHJvdmlkZXJBY2NlcHRlZEF0ISAtIDYwMDAwXG4gICAgICB8fCBpbnB1dC5vY2N1cnJlZEF0ID4gcmVjZWlwdC5wcm92aWRlckFjY2VwdGVkQXQhICsgKDMwICogODY0MDAwMDApKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2NybV9kZWxpdmVyeV9hY2NlcHRlZF9tZXNzYWdlX3JlcXVpcmVkJyk7XG4gICAgfVxuICAgIGNvbnN0IHVzZXIgPSBhd2FpdCBxdWVyeShhcWxgUkVUVVJOIERPQ1VNRU5UKCd1c2VycycsICR7cmVjZWlwdC5yZWNpcGllbnQuaWR9KWApXG4gICAgICAudGhlbigoY3Vyc29yKSA9PiBjdXJzb3IubmV4dCgpKSBhcyB7XG4gICAgICBfa2V5OiBzdHJpbmc7IGRlbGV0ZWQ/OiBib29sZWFuOyBlbWFpbDogc3RyaW5nOyBlbWFpbERlbGl2ZXJ5U3RhdHVzPzogc3RyaW5nO1xuICAgIH07XG4gICAgaWYoIXVzZXIgfHwgdXNlci5kZWxldGVkID09PSB0cnVlIHx8IHVzZXIuZW1haWwgIT09IHJlY2VpcHQucmVjaXBpZW50LmVtYWlsIHx8IHVzZXIuX2tleSAhPT0gcmVjZWlwdC5yZWNpcGllbnQuaWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignY3JtX2RlbGl2ZXJ5X3JlY2lwaWVudF9jaGFuZ2VkJyk7XG4gICAgfVxuICAgIGNvbnN0IGVyYXNlZCA9IGF3YWl0IHF1ZXJ5KGFxbGBSRVRVUk4gRE9DVU1FTlQoJ2NybURlbGl2ZXJ5UHJpdmFjeScsICR7Y3JtRGVsaXZlcnlQcml2YWN5S2V5KGlucHV0LmFwcElkLCB1c2VyLl9rZXkpfSlgKVxuICAgICAgLnRoZW4oKGN1cnNvcikgPT4gY3Vyc29yLm5leHQoKSkgYXMge2VyYXNlZEF0OiBudW1iZXJ9IHwgdW5kZWZpbmVkO1xuICAgIGlmKGVyYXNlZCAmJiByZWNlaXB0LnByb3ZpZGVyQWNjZXB0ZWRBdCEgPD0gZXJhc2VkLmVyYXNlZEF0KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2NybV9kZWxpdmVyeV9wcml2YWN5X2VyYXNlZCcpO1xuICAgIH1cbiAgICBjb25zdCBpZCA9IGRpZ2VzdChbaW5wdXQuYXBwSWQsIGlucHV0LmV2ZW50SWRdKTtcbiAgICBjb25zdCBmaW5nZXJwcmludCA9IGRpZ2VzdChbaW5wdXQuYXBwSWQsIGlucHV0LnJlY2VpcHRJZCwgaW5wdXQucHJvdmlkZXJNZXNzYWdlSWQsIGlucHV0LmtpbmQsXG4gICAgICBpbnB1dC5ib3VuY2VUeXBlLCBpbnB1dC5vY2N1cnJlZEF0XSk7XG4gICAgY29uc3QgcHJldmlvdXMgPSBhd2FpdCBxdWVyeShhcWxgUkVUVVJOIERPQ1VNRU5UKCdjcm1EZWxpdmVyeUV2ZW50cycsICR7aWR9KWApXG4gICAgICAudGhlbigoY3Vyc29yKSA9PiBjdXJzb3IubmV4dCgpKSBhcyB7ZmluZ2VycHJpbnQ6IHN0cmluZzsgcmVjZWlwdDogQ3JtRGVsaXZlcnlFdmVudFJlY2VpcHR9IHwgdW5kZWZpbmVkO1xuICAgIGlmKHByZXZpb3VzICYmIHByZXZpb3VzLmZpbmdlcnByaW50ICE9PSBmaW5nZXJwcmludCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdjcm1fZGVsaXZlcnlfZXZlbnRfY29uZmxpY3QnKTtcbiAgICB9XG4gICAgY29uc3QgY3VycmVudCA9IGF3YWl0IGF1dGhvcml6ZUNybUV4ZWN1dGlvbkdyYW50KGNvbnRleHQsIGlucHV0LmFwcElkLCAnY3JtOmRlbGl2ZXJ5X2V2ZW50cycsIG5vdygpKTtcbiAgICBpZihjdXJyZW50LmlkICE9PSBncmFudC5pZCB8fCBjdXJyZW50Lmhhc2ggIT09IGdyYW50Lmhhc2ggfHwgY3VycmVudC5kYXRhYmFzZU5hbWUgIT09IGdyYW50LmRhdGFiYXNlTmFtZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdjcm1fZGVsaXZlcnlfcmVsYXlfY2hhbmdlZCcpO1xuICAgIH1cbiAgICBjb25zdCBzdXBwcmVzc2VkID0gaW5wdXQua2luZCAhPT0gJ2RlbGl2ZXJlZCcgfHwgQm9vbGVhbih1c2VyLmVtYWlsRGVsaXZlcnlTdGF0dXMgJiYgdXNlci5lbWFpbERlbGl2ZXJ5U3RhdHVzICE9PSAndmFsaWQnKTtcbiAgICBpZihwcmV2aW91cykge1xuICAgICAgYXdhaXQgdHguY29tbWl0KCk7XG4gICAgICByZXR1cm4gey4uLnByZXZpb3VzLnJlY2VpcHQsIHN1cHByZXNzZWR9O1xuICAgIH1cbiAgICBjb25zdCB0aW1lbGluZSA9IGF3YWl0IHF1ZXJ5KGFxbGBGT1IgZXZlbnQgSU4gY3JtRGVsaXZlcnlFdmVudHNcbiAgICAgIEZJTFRFUiBldmVudC5hcHBJZCA9PSAke2lucHV0LmFwcElkfSBBTkQgZXZlbnQucmVjZWlwdC5yZWNlaXB0SWQgPT0gJHtpbnB1dC5yZWNlaXB0SWR9XG4gICAgICBMSU1JVCAzMiBSRVRVUk4gZXZlbnQuX2tleWApLnRoZW4oKGN1cnNvcikgPT4gY3Vyc29yLmFsbCgpKSBhcyBzdHJpbmdbXTtcbiAgICBpZih0aW1lbGluZS5sZW5ndGggPj0gMzIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignY3JtX2RlbGl2ZXJ5X3RpbWVsaW5lX2xpbWl0Jyk7XG4gICAgfVxuICAgIGNvbnN0IHJlc3VsdDogQ3JtRGVsaXZlcnlFdmVudFJlY2VpcHQgPSB7Ym91bmNlVHlwZTogaW5wdXQuYm91bmNlVHlwZSwgaWQsIGtpbmQ6IGlucHV0LmtpbmQsXG4gICAgICBvY2N1cnJlZEF0OiBpbnB1dC5vY2N1cnJlZEF0LCByZWNlaXB0SWQ6IGlucHV0LnJlY2VpcHRJZCwgc3VwcHJlc3NlZH07XG4gICAgYXdhaXQgcXVlcnkoYXFsYElOU0VSVCAke3tfa2V5OiBpZCwgYXBwSWQ6IGlucHV0LmFwcElkLCBmaW5nZXJwcmludCwgcmVjZWlwdDogcmVzdWx0LCByZWNlaXZlZEF0OiBub3coKSxcbiAgICAgIHJlbGF5R3JhbnRJZDogZ3JhbnQuaWR9fSBJTlRPIGNybURlbGl2ZXJ5RXZlbnRzYCk7XG4gICAgaWYoaW5wdXQua2luZCAhPT0gJ2RlbGl2ZXJlZCcpIHtcbiAgICAgIC8vIEFueSBib3VuY2UgaG9sZHMgc2VuZHM7IGNsYXNzaWZpY2F0aW9uIGlzIGV2aWRlbmNlLCBub3QgYSBjbGFpbSB0aGF0IGEgdHJhbnNpZW50IGJvdW5jZSBpcyBwZXJtYW5lbnQuXG4gICAgICBjb25zdCBzdGF0dXMgPSBpbnB1dC5raW5kID09PSAnY29tcGxhaW50JyB8fCB1c2VyLmVtYWlsRGVsaXZlcnlTdGF0dXMgPT09ICdjb21wbGFpbnQnID8gJ2NvbXBsYWludCcgOiAnYm91bmNlX2hvbGQnO1xuICAgICAgYXdhaXQgcXVlcnkoYXFsYFVQREFURSAke3VzZXIuX2tleX0gV0lUSCB7ZW1haWxEZWxpdmVyeVN0YXR1czogJHtzdGF0dXN9LCBtb2RpZmllZDogJHtub3coKX19IElOIHVzZXJzYCk7XG4gICAgfVxuICAgIGF3YWl0IHF1ZXJ5KGFxbGBJTlNFUlQgJHt7X2Zyb206IGBjcm1FeGVjdXRpb25SZWNlaXB0cy8ke2lucHV0LnJlY2VpcHRJZH1gLCBfa2V5OiBpZCxcbiAgICAgIF90bzogYGNybURlbGl2ZXJ5RXZlbnRzLyR7aWR9YCwgYXBwSWQ6IGlucHV0LmFwcElkfX0gSU5UTyBoYXNDcm1FeGVjdXRpb25EZWxpdmVyeWApO1xuICAgIGF3YWl0IHF1ZXJ5KGFxbGBJTlNFUlQgJHt7X2Zyb206IGB1c2Vycy8ke3VzZXIuX2tleX1gLCBfa2V5OiBpZCxcbiAgICAgIF90bzogYGNybURlbGl2ZXJ5RXZlbnRzLyR7aWR9YCwgYXBwSWQ6IGlucHV0LmFwcElkfX0gSU5UTyBoYXNVc2VyQ3JtRGVsaXZlcnlgKTtcbiAgICBjb25zdCBjbG9zaW5nID0gYXdhaXQgYXV0aG9yaXplQ3JtRXhlY3V0aW9uR3JhbnQoY29udGV4dCwgaW5wdXQuYXBwSWQsICdjcm06ZGVsaXZlcnlfZXZlbnRzJywgbm93KCkpO1xuICAgIGlmKGNsb3NpbmcuaWQgIT09IGdyYW50LmlkIHx8IGNsb3NpbmcuaGFzaCAhPT0gZ3JhbnQuaGFzaCB8fCBjbG9zaW5nLmRhdGFiYXNlTmFtZSAhPT0gZ3JhbnQuZGF0YWJhc2VOYW1lKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2NybV9kZWxpdmVyeV9yZWxheV9jaGFuZ2VkJyk7XG4gICAgfVxuICAgIGF3YWl0IHR4LmNvbW1pdCgpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH0gY2F0Y2goZXJyb3IpIHtcbiAgICBhd2FpdCB0eC5hYm9ydCgpO1xuICAgIHRocm93IGVycm9yO1xuICB9XG59O1xuXG5cbi8qKiBOYXRpdmUgYWRtaW5pc3RyYXRvciBwcml2YWN5IGFjdGlvbjsgcGVyc2lzdGVudCBtaW5pbWl6ZWQgdG9tYnN0b25lIHByZXZlbnRzIHByaW9yIGFjY2VwdGVkLW1lc3NhZ2UgcmVpbmdlc3Rpb24uICovXG5leHBvcnQgY29uc3QgZXJhc2VDcm1EZWxpdmVyeUV2aWRlbmNlID0gYXN5bmMgKFxuICBjb250ZXh0OiBBcGlDb250ZXh0LCBhcHBJZDogc3RyaW5nLCB1c2VySWQ6IHN0cmluZywgbm93ID0gRGF0ZS5ub3dcbik6IFByb21pc2U8e2VyYXNlZDogbnVtYmVyfT4gPT4ge1xuICByZXF1aXJlQ3JtQWRtaW5pc3RyYXRvcihjb250ZXh0KTtcbiAgaWYoIWlkZW50aWZpZXIoYXBwSWQpIHx8IGFwcElkLmxlbmd0aCA+IDEwMCB8fCAhaWRlbnRpZmllcih1c2VySWQpIHx8IHVzZXJJZC5sZW5ndGggPiAxMDApIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ2ludmFsaWRfY3JtX2RlbGl2ZXJ5X2VyYXN1cmUnKTtcbiAgfVxuICBjb25zdCBzY29wZWQgPSBhd2FpdCBzY29wZUNvbnRleHRUb0FwcChjb250ZXh0LCBhcHBJZCk7XG4gIGNvbnN0IGRiID0gdXNlRGIoc2NvcGVkLmRhdGFiYXNlTmFtZSk7XG4gIGNvbnN0IGRvY3VtZW50cyA9IFsnY3JtRGVsaXZlcnlFdmVudHMnLCAnY3JtRGVsaXZlcnlQcml2YWN5J107XG4gIGNvbnN0IGVkZ2VzID0gWydoYXNDcm1FeGVjdXRpb25EZWxpdmVyeScsICdoYXNVc2VyQ3JtRGVsaXZlcnknXTtcbiAgZm9yKGNvbnN0IG5hbWUgb2YgWy4uLmRvY3VtZW50cywgLi4uZWRnZXNdKSB7XG4gICAgYXdhaXQgZW5zdXJlT3duZXJFeGVjdXRpb25Db2xsZWN0aW9uKGRiLCBuYW1lLCBlZGdlcy5pbmNsdWRlcyhuYW1lKSk7XG4gIH1cbiAgY29uc3QgdHggPSBhd2FpdCBkYi5iZWdpblRyYW5zYWN0aW9uKHtleGNsdXNpdmU6IFsuLi5kb2N1bWVudHMsIC4uLmVkZ2VzXX0sIHtsb2NrVGltZW91dDogMTB9KTtcbiAgY29uc3QgcXVlcnkgPSAodmFsdWU6IFJldHVyblR5cGU8dHlwZW9mIGFxbD4pID0+IHR4LnN0ZXAoKCkgPT4gZGIucXVlcnkodmFsdWUpKTtcbiAgdHJ5IHtcbiAgICBjb25zdCBpZHMgPSBhd2FpdCBxdWVyeShhcWxgRk9SIGVkZ2UgSU4gaGFzVXNlckNybURlbGl2ZXJ5XG4gICAgICBGSUxURVIgZWRnZS5hcHBJZCA9PSAke2FwcElkfSBBTkQgZWRnZS5fZnJvbSA9PSAke2B1c2Vycy8ke3VzZXJJZH1gfVxuICAgICAgUkVUVVJOIFBBUlNFX0lERU5USUZJRVIoZWRnZS5fdG8pLmtleWApLnRoZW4oKGN1cnNvcikgPT4gY3Vyc29yLmFsbCgpKSBhcyBzdHJpbmdbXTtcbiAgICBjb25zdCBpZCA9IGNybURlbGl2ZXJ5UHJpdmFjeUtleShhcHBJZCwgdXNlcklkKTtcbiAgICBhd2FpdCBxdWVyeShhcWxgVVBTRVJUIHtfa2V5OiAke2lkfX0gSU5TRVJUICR7e19rZXk6IGlkLCBlcmFzZWRBdDogbm93KCl9fVxuICAgICAgVVBEQVRFIHtlcmFzZWRBdDogTUFYKFtPTEQuZXJhc2VkQXQsICR7bm93KCl9XSl9IElOIGNybURlbGl2ZXJ5UHJpdmFjeWApO1xuICAgIGF3YWl0IHF1ZXJ5KGFxbGBGT1IgZXZlbnQgSU4gY3JtRGVsaXZlcnlFdmVudHMgRklMVEVSIGV2ZW50Ll9rZXkgSU4gJHtpZHN9IFJFTU9WRSBldmVudCBJTiBjcm1EZWxpdmVyeUV2ZW50c2ApO1xuICAgIGF3YWl0IHF1ZXJ5KGFxbGBGT1IgZWRnZSBJTiBoYXNDcm1FeGVjdXRpb25EZWxpdmVyeSBGSUxURVIgZWRnZS5hcHBJZCA9PSAke2FwcElkfVxuICAgICAgQU5EIFBBUlNFX0lERU5USUZJRVIoZWRnZS5fdG8pLmtleSBJTiAke2lkc30gUkVNT1ZFIGVkZ2UgSU4gaGFzQ3JtRXhlY3V0aW9uRGVsaXZlcnlgKTtcbiAgICBhd2FpdCBxdWVyeShhcWxgRk9SIGVkZ2UgSU4gaGFzVXNlckNybURlbGl2ZXJ5IEZJTFRFUiBlZGdlLmFwcElkID09ICR7YXBwSWR9XG4gICAgICBBTkQgZWRnZS5fZnJvbSA9PSAke2B1c2Vycy8ke3VzZXJJZH1gfSBSRU1PVkUgZWRnZSBJTiBoYXNVc2VyQ3JtRGVsaXZlcnlgKTtcbiAgICBhd2FpdCB0eC5jb21taXQoKTtcbiAgICByZXR1cm4ge2VyYXNlZDogaWRzLmxlbmd0aH07XG4gIH0gY2F0Y2goZXJyb3IpIHtcbiAgICBhd2FpdCB0eC5hYm9ydCgpO1xuICAgIHRocm93IGVycm9yO1xuICB9XG59O1xuIl0sIm5hbWVzIjpbImFxbCIsImNyZWF0ZUhhc2giLCJzY29wZUNvbnRleHRUb0FwcCIsInVzZURiIiwicmVxdWlyZUNybUFkbWluaXN0cmF0b3IiLCJjcm1EZWxpdmVyeVByaXZhY3lLZXkiLCJhdXRob3JpemVDcm1FeGVjdXRpb25HcmFudCIsImVuc3VyZU93bmVyRXhlY3V0aW9uQ29sbGVjdGlvbiIsImRpZ2VzdCIsInZhbHVlIiwidXBkYXRlIiwiSlNPTiIsInN0cmluZ2lmeSIsImlkZW50aWZpZXIiLCJ0ZXN0IiwidmFsaWQiLCJpbnB1dCIsImF0IiwiQm9vbGVhbiIsImFwcElkIiwibGVuZ3RoIiwiZXZlbnRJZCIsInJlY2VpcHRJZCIsInByb3ZpZGVyTWVzc2FnZUlkIiwiaW5jbHVkZXMiLCJraW5kIiwiT2JqZWN0Iiwia2V5cyIsImV2ZXJ5Iiwia2V5IiwiYm91bmNlVHlwZSIsInVuZGVmaW5lZCIsIk51bWJlciIsImlzU2FmZUludGVnZXIiLCJvY2N1cnJlZEF0IiwiaW5nZXN0Q3JtRGVsaXZlcnlFdmVudCIsImNvbnRleHQiLCJub3ciLCJEYXRlIiwiRXJyb3IiLCJncmFudCIsImRiIiwiZGF0YWJhc2VOYW1lIiwiZG9jdW1lbnRzIiwiZWRnZXMiLCJuYW1lIiwidHgiLCJiZWdpblRyYW5zYWN0aW9uIiwiZXhjbHVzaXZlIiwibG9ja1RpbWVvdXQiLCJxdWVyeSIsInN0ZXAiLCJyZWNlaXB0IiwidGhlbiIsImN1cnNvciIsIm5leHQiLCJpZCIsInN0YXRlIiwicHJvdmlkZXJBY2NlcHRlZEF0IiwiY3JlYXRlZEF0IiwidXNlciIsInJlY2lwaWVudCIsImRlbGV0ZWQiLCJlbWFpbCIsIl9rZXkiLCJlcmFzZWQiLCJlcmFzZWRBdCIsImZpbmdlcnByaW50IiwicHJldmlvdXMiLCJjdXJyZW50IiwiaGFzaCIsInN1cHByZXNzZWQiLCJlbWFpbERlbGl2ZXJ5U3RhdHVzIiwiY29tbWl0IiwidGltZWxpbmUiLCJhbGwiLCJyZXN1bHQiLCJyZWNlaXZlZEF0IiwicmVsYXlHcmFudElkIiwic3RhdHVzIiwiX2Zyb20iLCJfdG8iLCJjbG9zaW5nIiwiZXJyb3IiLCJhYm9ydCIsImVyYXNlQ3JtRGVsaXZlcnlFdmlkZW5jZSIsInVzZXJJZCIsInNjb3BlZCIsImlkcyJdLCJtYXBwaW5ncyI6IkFBQUEsbUhBQW1ILEdBQ25ILFNBQVFBLEdBQUcsUUFBTyxXQUFXO0FBQzdCLFNBQVFDLFVBQVUsUUFBTyxjQUFjO0FBRXZDLFNBQVFDLGlCQUFpQixRQUFPLHlCQUF5QjtBQUN6RCxTQUFRQyxLQUFLLFFBQU8sNEJBQTRCO0FBQ2hELFNBQVFDLHVCQUF1QixRQUFPLFdBQVc7QUFDakQsU0FBUUMs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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { useDb } from '../utils/arangodbUtils.js';
|
|
2
|
+
import type { CrmExecutionReceipt } from './crmExecution.js';
|
|
3
|
+
export interface CrmDeliveryEvidence {
|
|
4
|
+
events: {
|
|
5
|
+
bounceType?: 'Permanent' | 'Transient' | 'Undetermined';
|
|
6
|
+
kind: 'delivered' | 'bounce' | 'complaint';
|
|
7
|
+
occurredAt: number;
|
|
8
|
+
}[];
|
|
9
|
+
generatedAt: number;
|
|
10
|
+
negativeHold: boolean | null;
|
|
11
|
+
schemaVersion: 'crm_delivery_evidence_v1';
|
|
12
|
+
status: 'available' | 'erased' | 'unavailable';
|
|
13
|
+
validUntil: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const crmDeliveryPrivacyKey: (appId: string, userId: string) => string;
|
|
16
|
+
/** Internal projection composed only after the ordinary exact native receipt-read authority check. */
|
|
17
|
+
export declare const readCrmDeliveryEvidence: (db: ReturnType<typeof useDb>, receipt: CrmExecutionReceipt, now: number, expires: number) => Promise<CrmDeliveryEvidence>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle -- Native minimized owner recipient and ledger metadata. */ import { aql } from 'arangojs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
export const crmDeliveryPrivacyKey = (appId, userId)=>createHash('sha256').update(JSON.stringify([
|
|
4
|
+
appId,
|
|
5
|
+
userId
|
|
6
|
+
])).digest('hex');
|
|
7
|
+
/** Internal projection composed only after the ordinary exact native receipt-read authority check. */ export const readCrmDeliveryEvidence = async (db, receipt, now, expires)=>{
|
|
8
|
+
const base = {
|
|
9
|
+
events: [],
|
|
10
|
+
generatedAt: now,
|
|
11
|
+
negativeHold: null,
|
|
12
|
+
schemaVersion: 'crm_delivery_evidence_v1',
|
|
13
|
+
status: 'unavailable',
|
|
14
|
+
validUntil: Math.min(expires, now + 900000)
|
|
15
|
+
};
|
|
16
|
+
if (receipt.state !== 'applied' || !receipt.providerMessageId || !Number.isSafeInteger(receipt.providerAcceptedAt) || ![
|
|
17
|
+
'crm_outreach',
|
|
18
|
+
'scheduling_reminder'
|
|
19
|
+
].includes(receipt.kind) || expires <= now || !await db.collection('crmDeliveryEvents').exists() || !await db.collection('crmDeliveryPrivacy').exists()) {
|
|
20
|
+
return base;
|
|
21
|
+
}
|
|
22
|
+
const row = await db.query(aql`LET user = DOCUMENT('users', ${receipt.recipient.id})
|
|
23
|
+
LET privacy = DOCUMENT('crmDeliveryPrivacy', ${crmDeliveryPrivacyKey(receipt.appId, receipt.recipient.id)})
|
|
24
|
+
LET events = (FOR event IN crmDeliveryEvents
|
|
25
|
+
FILTER event.appId == ${receipt.appId} AND event.receipt.receiptId == ${receipt.id}
|
|
26
|
+
SORT event.receipt.occurredAt ASC, event._key ASC LIMIT 33
|
|
27
|
+
RETURN KEEP(event.receipt, 'bounceType', 'kind', 'occurredAt'))
|
|
28
|
+
RETURN {events, privacy, user: user ? KEEP(user, '_key', 'deleted', 'email', 'emailDeliveryStatus') : null}`).then((cursor)=>cursor.next());
|
|
29
|
+
if (row?.privacy && receipt.providerAcceptedAt <= row.privacy.erasedAt) {
|
|
30
|
+
return {
|
|
31
|
+
...base,
|
|
32
|
+
status: 'erased'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (!row?.user || row.user.deleted === true || row.user._key !== receipt.recipient.id || row.user.email !== receipt.recipient.email || !Array.isArray(row.events) || row.events.length > 32 || row.events.some((event)=>![
|
|
36
|
+
'delivered',
|
|
37
|
+
'bounce',
|
|
38
|
+
'complaint'
|
|
39
|
+
].includes(event.kind) || !Number.isSafeInteger(event.occurredAt) || event.occurredAt > now + 60000 || event.occurredAt < receipt.providerAcceptedAt - 60000 || (event.kind === 'bounce' ? ![
|
|
40
|
+
'Permanent',
|
|
41
|
+
'Transient',
|
|
42
|
+
'Undetermined'
|
|
43
|
+
].includes(event.bounceType || '') : event.bounceType !== undefined))) {
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
...base,
|
|
48
|
+
events: row.events.map((event)=>({
|
|
49
|
+
bounceType: event.bounceType,
|
|
50
|
+
kind: event.kind,
|
|
51
|
+
occurredAt: event.occurredAt
|
|
52
|
+
})),
|
|
53
|
+
negativeHold: Boolean(row.user.emailDeliveryStatus && row.user.emailDeliveryStatus !== 'valid'),
|
|
54
|
+
status: 'available'
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9uaXRyb2c3L0RldmVsb3BtZW50L3JlYWt0b3IvcGFja2FnZXMvcmVha3Rvci9zcmMvYWN0aW9ucy9jcm1EZWxpdmVyeUV2aWRlbmNlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIG5vLXVuZGVyc2NvcmUtZGFuZ2xlIC0tIE5hdGl2ZSBtaW5pbWl6ZWQgb3duZXIgcmVjaXBpZW50IGFuZCBsZWRnZXIgbWV0YWRhdGEuICovXG5pbXBvcnQge2FxbH0gZnJvbSAnYXJhbmdvanMnO1xuaW1wb3J0IHtjcmVhdGVIYXNofSBmcm9tICdub2RlOmNyeXB0byc7XG5cbmltcG9ydCB0eXBlIHt1c2VEYn0gZnJvbSAnLi4vdXRpbHMvYXJhbmdvZGJVdGlscy5qcyc7XG5pbXBvcnQgdHlwZSB7Q3JtRXhlY3V0aW9uUmVjZWlwdH0gZnJvbSAnLi9jcm1FeGVjdXRpb24uanMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIENybURlbGl2ZXJ5RXZpZGVuY2Uge1xuICBldmVudHM6IHtib3VuY2VUeXBlPzogJ1Blcm1hbmVudCcgfCAnVHJhbnNpZW50JyB8ICdVbmRldGVybWluZWQnOyBraW5kOiAnZGVsaXZlcmVkJyB8ICdib3VuY2UnIHwgJ2NvbXBsYWludCc7IG9jY3VycmVkQXQ6IG51bWJlcn1bXTtcbiAgZ2VuZXJhdGVkQXQ6IG51bWJlcjtcbiAgbmVnYXRpdmVIb2xkOiBib29sZWFuIHwgbnVsbDtcbiAgc2NoZW1hVmVyc2lvbjogJ2NybV9kZWxpdmVyeV9ldmlkZW5jZV92MSc7XG4gIHN0YXR1czogJ2F2YWlsYWJsZScgfCAnZXJhc2VkJyB8ICd1bmF2YWlsYWJsZSc7XG4gIHZhbGlkVW50aWw6IG51bWJlcjtcbn1cbmV4cG9ydCBjb25zdCBjcm1EZWxpdmVyeVByaXZhY3lLZXkgPSAoYXBwSWQ6IHN0cmluZywgdXNlcklkOiBzdHJpbmcpOiBzdHJpbmcgPT5cbiAgY3JlYXRlSGFzaCgnc2hhMjU2JykudXBkYXRlKEpTT04uc3RyaW5naWZ5KFthcHBJZCwgdXNlcklkXSkpLmRpZ2VzdCgnaGV4Jyk7XG5cbi8qKiBJbnRlcm5hbCBwcm9qZWN0aW9uIGNvbXBvc2VkIG9ubHkgYWZ0ZXIgdGhlIG9yZGluYXJ5IGV4YWN0IG5hdGl2ZSByZWNlaXB0LXJlYWQgYXV0aG9yaXR5IGNoZWNrLiAqL1xuZXhwb3J0IGNvbnN0IHJlYWRDcm1EZWxpdmVyeUV2aWRlbmNlID0gYXN5bmMgKFxuICBkYjogUmV0dXJuVHlwZTx0eXBlb2YgdXNlRGI+LCByZWNlaXB0OiBDcm1FeGVjdXRpb25SZWNlaXB0LCBub3c6IG51bWJlciwgZXhwaXJlczogbnVtYmVyXG4pOiBQcm9taXNlPENybURlbGl2ZXJ5RXZpZGVuY2U+ID0+IHtcbiAgY29uc3QgYmFzZTogQ3JtRGVsaXZlcnlFdmlkZW5jZSA9IHtldmVudHM6IFtdLCBnZW5lcmF0ZWRBdDogbm93LCBuZWdhdGl2ZUhvbGQ6IG51bGwsXG4gICAgc2NoZW1hVmVyc2lvbjogJ2NybV9kZWxpdmVyeV9ldmlkZW5jZV92MScsIHN0YXR1czogJ3VuYXZhaWxhYmxlJywgdmFsaWRVbnRpbDogTWF0aC5taW4oZXhwaXJlcywgbm93ICsgOTAwMDAwKX07XG4gIGlmKHJlY2VpcHQuc3RhdGUgIT09ICdhcHBsaWVkJyB8fCAhcmVjZWlwdC5wcm92aWRlck1lc3NhZ2VJZCB8fCAhTnVtYmVyLmlzU2FmZUludGVnZXIocmVjZWlwdC5wcm92aWRlckFjY2VwdGVkQXQpXG4gICAgfHwgIVsnY3JtX291dHJlYWNoJywgJ3NjaGVkdWxpbmdfcmVtaW5kZXInXS5pbmNsdWRlcyhyZWNlaXB0LmtpbmQpIHx8IGV4cGlyZXMgPD0gbm93XG4gICAgfHwgIWF3YWl0IGRiLmNvbGxlY3Rpb24oJ2NybURlbGl2ZXJ5RXZlbnRzJykuZXhpc3RzKCkgfHwgIWF3YWl0IGRiLmNvbGxlY3Rpb24oJ2NybURlbGl2ZXJ5UHJpdmFjeScpLmV4aXN0cygpKSB7XG4gICAgcmV0dXJuIGJhc2U7XG4gIH1cbiAgY29uc3Qgcm93ID0gYXdhaXQgZGIucXVlcnkoYXFsYExFVCB1c2VyID0gRE9DVU1FTlQoJ3VzZXJzJywgJHtyZWNlaXB0LnJlY2lwaWVudC5pZH0pXG4gICAgTEVUIHByaXZhY3kgPSBET0NVTUVOVCgnY3JtRGVsaXZlcnlQcml2YWN5JywgJHtjcm1EZWxpdmVyeVByaXZhY3lLZXkocmVjZWlwdC5hcHBJZCwgcmVjZWlwdC5yZWNpcGllbnQuaWQpfSlcbiAgICBMRVQgZXZlbnRzID0gKEZPUiBldmVudCBJTiBjcm1EZWxpdmVyeUV2ZW50c1xuICAgICAgRklMVEVSIGV2ZW50LmFwcElkID09ICR7cmVjZWlwdC5hcHBJZH0gQU5EIGV2ZW50LnJlY2VpcHQucmVjZWlwdElkID09ICR7cmVjZWlwdC5pZH1cbiAgICAgIFNPUlQgZXZlbnQucmVjZWlwdC5vY2N1cnJlZEF0IEFTQywgZXZlbnQuX2tleSBBU0MgTElNSVQgMzNcbiAgICAgIFJFVFVSTiBLRUVQKGV2ZW50LnJlY2VpcHQsICdib3VuY2VUeXBlJywgJ2tpbmQnLCAnb2NjdXJyZWRBdCcpKVxuICAgIFJFVFVSTiB7ZXZlbnRzLCBwcml2YWN5LCB1c2VyOiB1c2VyID8gS0VFUCh1c2VyLCAnX2tleScsICdkZWxldGVkJywgJ2VtYWlsJywgJ2VtYWlsRGVsaXZlcnlTdGF0dXMnKSA6IG51bGx9YClcbiAgICAudGhlbigoY3Vyc29yKSA9PiBjdXJzb3IubmV4dCgpKSBhcyB7ZXZlbnRzOiBDcm1EZWxpdmVyeUV2aWRlbmNlWydldmVudHMnXTsgcHJpdmFjeT86IHtlcmFzZWRBdDogbnVtYmVyfTtcbiAgICAgIHVzZXI/OiB7X2tleTogc3RyaW5nOyBkZWxldGVkPzogYm9vbGVhbjsgZW1haWw6IHN0cmluZzsgZW1haWxEZWxpdmVyeVN0YXR1cz86IHN0cmluZ307fSB8IHVuZGVmaW5lZDtcbiAgaWYocm93Py5wcml2YWN5ICYmIHJlY2VpcHQucHJvdmlkZXJBY2NlcHRlZEF0ISA8PSByb3cucHJpdmFjeS5lcmFzZWRBdCkge1xuICAgIHJldHVybiB7Li4uYmFzZSwgc3RhdHVzOiAnZXJhc2VkJ307XG4gIH1cbiAgaWYoIXJvdz8udXNlciB8fCByb3cudXNlci5kZWxldGVkID09PSB0cnVlIHx8IHJvdy51c2VyLl9rZXkgIT09IHJlY2VpcHQucmVjaXBpZW50LmlkXG4gICAgfHwgcm93LnVzZXIuZW1haWwgIT09IHJlY2VpcHQucmVjaXBpZW50LmVtYWlsIHx8ICFBcnJheS5pc0FycmF5KHJvdy5ldmVudHMpIHx8IHJvdy5ldmVudHMubGVuZ3RoID4gMzJcbiAgICB8fCByb3cuZXZlbnRzLnNvbWUoKGV2ZW50KSA9PiAhWydkZWxpdmVyZWQnLCAnYm91bmNlJywgJ2NvbXBsYWludCddLmluY2x1ZGVzKGV2ZW50LmtpbmQpXG4gICAgICB8fCAhTnVtYmVyLmlzU2FmZUludGVnZXIoZXZlbnQub2NjdXJyZWRBdCkgfHwgZXZlbnQub2NjdXJyZWRBdCA+IG5vdyArIDYwMDAwXG4gICAgICB8fCBldmVudC5vY2N1cnJlZEF0IDwgcmVjZWlwdC5wcm92aWRlckFjY2VwdGVkQXQhIC0gNjAwMDBcbiAgICAgIHx8IChldmVudC5raW5kID09PSAnYm91bmNlJyA/ICFbJ1Blcm1hbmVudCcsICdUcmFuc2llbnQnLCAnVW5kZXRlcm1pbmVkJ10uaW5jbHVkZXMoZXZlbnQuYm91bmNlVHlwZSB8fCAnJylcbiAgICAgICAgOiBldmVudC5ib3VuY2VUeXBlICE9PSB1bmRlZmluZWQpKSkge1xuICAgIHJldHVybiBiYXNlO1xuICB9XG4gIHJldHVybiB7Li4uYmFzZSwgZXZlbnRzOiByb3cuZXZlbnRzLm1hcCgoZXZlbnQpID0+ICh7Ym91bmNlVHlwZTogZXZlbnQuYm91bmNlVHlwZSxcbiAgICBraW5kOiBldmVudC5raW5kLCBvY2N1cnJlZEF0OiBldmVudC5vY2N1cnJlZEF0fSkpLFxuICBuZWdhdGl2ZUhvbGQ6IEJvb2xlYW4ocm93LnVzZXIuZW1haWxEZWxpdmVyeVN0YXR1cyAmJiByb3cudXNlci5lbWFpbERlbGl2ZXJ5U3RhdHVzICE9PSAndmFsaWQnKSwgc3RhdHVzOiAnYXZhaWxhYmxlJ307XG59O1xuIl0sIm5hbWVzIjpbImFxbCIsImNyZWF0ZUhhc2giLCJjcm1EZWxpdmVyeVByaXZhY3lLZXkiLCJhcHBJZCIsInVzZXJJZCIsInVwZGF0ZSIsIkpTT04iLCJzdHJpbmdpZnkiLCJkaWdlc3QiLCJyZWFkQ3JtRGVsaXZlcnlFdmlkZW5jZSIsImRiIiwicmVjZWlwdCIsIm5vdyIsImV4cGlyZXMiLCJiYXNlIiwiZXZlbnRzIiwiZ2VuZXJhdGVkQXQiLCJuZWdhdGl2ZUhvbGQiLCJzY2hlbWFWZXJzaW9uIiwic3RhdHVzIiwidmFsaWRVbnRpbCIsIk1hdGgiLCJtaW4iLCJzdGF0ZSIsInByb3ZpZGVyTWVzc2FnZUlkIiwiTnVtYmVyIiwiaXNTYWZlSW50ZWdlciIsInByb3ZpZGVyQWNjZXB0ZWRBdCIsImluY2x1ZGVzIiwia2luZCIsImNvbGxlY3Rpb24iLCJleGlzdHMiLCJyb3ciLCJxdWVyeSIsInJlY2lwaWVudCIsImlkIiwidGhlbiIsImN1cnNvciIsIm5leHQiLCJwcml2YWN5IiwiZXJhc2VkQXQiLCJ1c2VyIiwiZGVsZXRlZCIsIl9rZXkiLCJlbWFpbCIsIkFycmF5IiwiaXNBcnJheSIsImxlbmd0aCIsInNvbWUiLCJldmVudCIsIm9jY3VycmVkQXQiLCJib3VuY2VUeXBlIiwidW5kZWZpbmVkIiwibWFwIiwiQm9vbGVhbiIsImVtYWlsRGVsaXZlcnlTdGF0dXMiXSwibWFwcGluZ3MiOiJBQUFBLGdHQUFnRyxHQUNoRyxTQUFRQSxHQUFHLFFBQU8sV0FBVztBQUM3QixTQUFRQyxVQUFVLFFBQU8sY0FBYztBQWF2QyxPQUFPLE1BQU1DLHdCQUF3QixDQUFDQyxPQUFlQyxTQUNuREgsV0FBVyxVQUFVSSxNQUFNLENBQUNDLEtBQUtDLFNBQVMsQ0FBQztRQUFDSjtRQUFPQztLQUFPLEdBQUdJLE1BQU0sQ0FBQyxPQUFPO0FBRTdFLG9HQUFvRyxHQUNwRyxPQUFPLE1BQU1DLDBCQUEwQixPQUNyQ0MsSUFBOEJDLFNBQThCQyxLQUFhQztJQUV6RSxNQUFNQyxPQUE0QjtRQUFDQyxRQUFRLEVBQUU7UUFBRUMsYUFBYUo7UUFBS0ssY0FBYztRQUM3RUMsZUFBZTtRQUE0QkMsUUFBUTtRQUFlQyxZQUFZQyxLQUFLQyxHQUFHLENBQUNULFNBQVNELE1BQU07SUFBTztJQUMvRyxJQUFHRCxRQUFRWSxLQUFLLEtBQUssYUFBYSxDQUFDWixRQUFRYSxpQkFBaUIsSUFBSSxDQUFDQyxPQUFPQyxhQUFhLENBQUNmLFFBQVFnQixrQkFBa0IsS0FDM0csQ0FBQztRQUFDO1FBQWdCO0tBQXNCLENBQUNDLFFBQVEsQ0FBQ2pCLFFBQVFrQixJQUFJLEtBQUtoQixXQUFXRCxPQUM5RSxDQUFDLE1BQU1GLEdBQUdvQixVQUFVLENBQUMscUJBQXFCQyxNQUFNLE1BQU0sQ0FBQyxNQUFNckIsR0FBR29CLFVBQVUsQ0FBQyxzQkFBc0JDLE1BQU0sSUFBSTtRQUM5RyxPQUFPakI7SUFDVDtJQUNBLE1BQU1rQixNQUFNLE1BQU10QixHQUFHdUIsS0FBSyxDQUFDakMsR0FBRyxDQUFDLDZCQUE2QixFQUFFVyxRQUFRdUIsU0FBUyxDQUFDQyxFQUFFLENBQUM7aURBQ3BDLEVBQUVqQyxzQkFBc0JTLFFBQVFSLEtBQUssRUFBRVEsUUFBUXVCLFNBQVMsQ0FBQ0MsRUFBRSxFQUFFOzs0QkFFbEYsRUFBRXhCLFFBQVFSLEtBQUssQ0FBQyxnQ0FBZ0MsRUFBRVEsUUFBUXdCLEVBQUUsQ0FBQzs7OytHQUdzQixDQUFDLEVBQzNHQyxJQUFJLENBQUMsQ0FBQ0MsU0FBV0EsT0FBT0MsSUFBSTtJQUUvQixJQUFHTixLQUFLTyxXQUFXNUIsUUFBUWdCLGtCQUFrQixJQUFLSyxJQUFJTyxPQUFPLENBQUNDLFFBQVEsRUFBRTtRQUN0RSxPQUFPO1lBQUMsR0FBRzFCLElBQUk7WUFBRUssUUFBUTtRQUFRO0lBQ25DO0lBQ0EsSUFBRyxDQUFDYSxLQUFLUyxRQUFRVCxJQUFJUyxJQUFJLENBQUNDLE9BQU8sS0FBSyxRQUFRVixJQUFJUyxJQUFJLENBQUNFLElBQUksS0FBS2hDLFFBQVF1QixTQUFTLENBQUNDLEVBQUUsSUFDL0VILElBQUlTLElBQUksQ0FBQ0csS0FBSyxLQUFLakMsUUFBUXVCLFNBQVMsQ0FBQ1UsS0FBSyxJQUFJLENBQUNDLE1BQU1DLE9BQU8sQ0FBQ2QsSUFBSWpCLE1BQU0sS0FBS2lCLElBQUlqQixNQUFNLENBQUNnQyxNQUFNLEdBQUcsTUFDaEdmLElBQUlqQixNQUFNLENBQUNpQyxJQUFJLENBQUMsQ0FBQ0MsUUFBVSxDQUFDO1lBQUM7WUFBYTtZQUFVO1NBQVksQ0FBQ3JCLFFBQVEsQ0FBQ3FCLE1BQU1wQixJQUFJLEtBQ2xGLENBQUNKLE9BQU9DLGFBQWEsQ0FBQ3VCLE1BQU1DLFVBQVUsS0FBS0QsTUFBTUMsVUFBVSxHQUFHdEMsTUFBTSxTQUNwRXFDLE1BQU1DLFVBQVUsR0FBR3ZDLFFBQVFnQixrQkFBa0IsR0FBSSxTQUNoRHNCLENBQUFBLE1BQU1wQixJQUFJLEtBQUssV0FBVyxDQUFDO1lBQUM7WUFBYTtZQUFhO1NBQWUsQ0FBQ0QsUUFBUSxDQUFDcUIsTUFBTUUsVUFBVSxJQUFJLE1BQ25HRixNQUFNRSxVQUFVLEtBQUtDLFNBQVEsSUFBSztRQUN4QyxPQUFPdEM7SUFDVDtJQUNBLE9BQU87UUFBQyxHQUFHQSxJQUFJO1FBQUVDLFFBQVFpQixJQUFJakIsTUFBTSxDQUFDc0MsR0FBRyxDQUFDLENBQUNKLFFBQVcsQ0FBQTtnQkFBQ0UsWUFBWUYsTUFBTUUsVUFBVTtnQkFDL0V0QixNQUFNb0IsTUFBTXBCLElBQUk7Z0JBQUVxQixZQUFZRCxNQUFNQyxVQUFVO1lBQUEsQ0FBQTtRQUNoRGpDLGNBQWNxQyxRQUFRdEIsSUFBSVMsSUFBSSxDQUFDYyxtQkFBbUIsSUFBSXZCLElBQUlTLElBQUksQ0FBQ2MsbUJBQW1CLEtBQUs7UUFBVXBDLFFBQVE7SUFBVztBQUN0SCxFQUFFIn0=
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useDb } from '../utils/arangodbUtils.js';
|
|
2
|
+
import { sesSend } from './email.js';
|
|
3
|
+
import type { ApiContext } from '../types/auth.types.js';
|
|
4
|
+
import type { CrmCheckoutRecoveryBinding, CrmCheckoutRecoveryBounds } from './crmCheckoutRecoveryPolicy.js';
|
|
5
|
+
import type { CrmDeliveryEvidence } from './crmDeliveryEvidence.js';
|
|
6
|
+
import type { CrmLeadFollowupBinding, CrmLeadFollowupBounds } from './crmLeadFollowups.js';
|
|
7
|
+
import type { CrmWinbackBinding } from './crmWinback.js';
|
|
8
|
+
import type { CrmWinbackBounds } from './crmWinbackPolicy.js';
|
|
9
|
+
import type { SupportManagement } from './supportPolicy.js';
|
|
10
|
+
export type CrmExecutionScope = 'crm:checkout_recovery' | 'crm:delivery_events' | 'crm:lead_followup' | 'crm:outreach' | 'crm:winback' | 'support:commerce' | 'support:conversation' | 'support:summary' | 'support:email_reply' | 'support:delivery_events' | 'support:channel_ingest' | 'support:context' | 'support:read' | 'support:triage' | 'support:reply' | 'support:manage' | 'scheduling:calendar_sync' | 'scheduling:calendar_invite' | 'scheduling:read' | 'scheduling:book' | 'scheduling:remind' | 'finance:provider_read' | 'finance:read' | 'finance:invoice';
|
|
11
|
+
export interface CrmExecutionGrantInput {
|
|
12
|
+
appId: string;
|
|
13
|
+
expiresAt: string;
|
|
14
|
+
followupBounds?: CrmLeadFollowupBounds;
|
|
15
|
+
recoveryBounds?: CrmCheckoutRecoveryBounds;
|
|
16
|
+
winbackBounds?: CrmWinbackBounds;
|
|
17
|
+
scopes: CrmExecutionScope[];
|
|
18
|
+
senderEmail: string;
|
|
19
|
+
unsubscribeUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CrmExecutionInput {
|
|
22
|
+
appId: string;
|
|
23
|
+
body: string;
|
|
24
|
+
expectedSourceRevision: string;
|
|
25
|
+
includeVerifiedRecoveryLink?: boolean;
|
|
26
|
+
followup?: CrmLeadFollowupBinding;
|
|
27
|
+
recovery?: CrmCheckoutRecoveryBinding;
|
|
28
|
+
winback?: CrmWinbackBinding;
|
|
29
|
+
idempotencyKey: string;
|
|
30
|
+
kind: 'crm_outreach' | 'support_triage' | 'support_reply' | 'support_manage' | 'scheduling_reminder';
|
|
31
|
+
management?: SupportManagement;
|
|
32
|
+
status?: 'in_progress' | 'waiting_on_customer' | 'waiting_on_internal';
|
|
33
|
+
subject: string;
|
|
34
|
+
targetId: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CrmExecutionReceipt {
|
|
37
|
+
appId: string;
|
|
38
|
+
body?: string;
|
|
39
|
+
contentStatus?: 'current' | 'redacted';
|
|
40
|
+
createdAt: number;
|
|
41
|
+
deliveryEvidence?: CrmDeliveryEvidence;
|
|
42
|
+
expiresAt: number;
|
|
43
|
+
followup?: CrmLeadFollowupBinding;
|
|
44
|
+
recovery?: CrmCheckoutRecoveryBinding;
|
|
45
|
+
winback?: CrmWinbackBinding;
|
|
46
|
+
grantId: string;
|
|
47
|
+
id: string;
|
|
48
|
+
kind: CrmExecutionInput['kind'];
|
|
49
|
+
management?: SupportManagement;
|
|
50
|
+
maxActions: 1;
|
|
51
|
+
notBefore?: number;
|
|
52
|
+
providerAcceptedAt?: number;
|
|
53
|
+
providerMessageId?: string;
|
|
54
|
+
recipient: {
|
|
55
|
+
email?: string;
|
|
56
|
+
id: string;
|
|
57
|
+
};
|
|
58
|
+
recoveryLinkHash?: string;
|
|
59
|
+
revision: string;
|
|
60
|
+
sourceRevision: string;
|
|
61
|
+
scheduled?: boolean;
|
|
62
|
+
scheduleStatus?: 'scheduled' | 'cancelled' | 'held' | 'completed';
|
|
63
|
+
scheduleUpdatedAt?: number;
|
|
64
|
+
state: 'pending' | 'approved' | 'rejected' | 'submitting' | 'unknown' | 'applied';
|
|
65
|
+
status?: CrmExecutionInput['status'];
|
|
66
|
+
subject?: string;
|
|
67
|
+
targetId: string;
|
|
68
|
+
}
|
|
69
|
+
interface Grant extends CrmExecutionGrantInput {
|
|
70
|
+
databaseName: string;
|
|
71
|
+
expires: number;
|
|
72
|
+
hash: string;
|
|
73
|
+
id: string;
|
|
74
|
+
ownerId: string;
|
|
75
|
+
revokedAt?: number;
|
|
76
|
+
}
|
|
77
|
+
interface Source {
|
|
78
|
+
email: string;
|
|
79
|
+
id: string;
|
|
80
|
+
revision: string;
|
|
81
|
+
notBefore?: number;
|
|
82
|
+
reminderConsentId?: string;
|
|
83
|
+
subject?: string;
|
|
84
|
+
ticketStatus?: string;
|
|
85
|
+
}
|
|
86
|
+
/** Native hosts can expire capability content in bounded maintenance without altering send claims. */
|
|
87
|
+
export declare const retainCrmCheckoutRecoveryContent: (context: ApiContext, appId: string, now?: () => number) => Promise<number>;
|
|
88
|
+
export declare const ensureOwnerExecutionCollection: (db: ReturnType<typeof useDb>, name: string, edge?: boolean) => Promise<void>;
|
|
89
|
+
/** Grant writes are administrator-only; the secret is returned once and never stored. */
|
|
90
|
+
export declare const createCrmExecutionKey: (context: ApiContext, input: CrmExecutionGrantInput, now?: () => number) => Promise<{
|
|
91
|
+
appId: string;
|
|
92
|
+
executionKey: string;
|
|
93
|
+
expiresAt: string;
|
|
94
|
+
followupBounds: CrmLeadFollowupBounds;
|
|
95
|
+
id: `${string}-${string}-${string}-${string}-${string}`;
|
|
96
|
+
recoveryBounds: CrmCheckoutRecoveryBounds;
|
|
97
|
+
scopes: CrmExecutionScope[];
|
|
98
|
+
winbackBounds: CrmWinbackBounds;
|
|
99
|
+
}>;
|
|
100
|
+
export declare const revokeCrmExecutionKey: (context: ApiContext, appId: string, id: string, now?: () => number) => Promise<boolean>;
|
|
101
|
+
export declare const authorizeCrmExecutionGrant: (context: ApiContext, appId: string, scope: CrmExecutionScope, now: number) => Promise<Grant>;
|
|
102
|
+
declare const source: (db: ReturnType<typeof useDb>, kind: CrmExecutionInput['kind'], targetId: string, query?: any, at?: number, appId?: string) => Promise<Source>;
|
|
103
|
+
/** Source policy shared by already-authorized owner workflows; never exposed as an HTTP resolver. */
|
|
104
|
+
export declare const readCrmExecutionTargetSource: typeof source;
|
|
105
|
+
/** Minimized authorized target read; no ticket descriptions, history or unrelated customer fields. */
|
|
106
|
+
export declare const readCrmExecutionTarget: (context: ApiContext, appId: string, kind: CrmExecutionInput['kind'], targetId: string, now?: () => number) => Promise<{
|
|
107
|
+
email: string;
|
|
108
|
+
id: string;
|
|
109
|
+
revision: string;
|
|
110
|
+
notBefore?: number;
|
|
111
|
+
reminderConsentId?: string;
|
|
112
|
+
subject?: string;
|
|
113
|
+
ticketStatus?: string;
|
|
114
|
+
appId: string;
|
|
115
|
+
targetId: string;
|
|
116
|
+
}>;
|
|
117
|
+
export declare const getCrmExecutionReceipt: (context: ApiContext, appId: string, id: string, now?: () => number) => Promise<CrmExecutionReceipt>;
|
|
118
|
+
/** Freeze exactly one recipient and plain-text action. Approval is a separate revision-bound operation. */
|
|
119
|
+
export declare const previewCrmExecution: (context: ApiContext, input: CrmExecutionInput, now?: () => number) => Promise<CrmExecutionReceipt>;
|
|
120
|
+
export declare const decideCrmExecution: (context: ApiContext, appId: string, id: string, revision: string, decision: 'approved' | 'rejected', now?: () => number) => Promise<CrmExecutionReceipt>;
|
|
121
|
+
/** Customer-facing Support reply/triage commits atomically. SES submissions are never blindly retried. */
|
|
122
|
+
export declare const executeCrmExecution: (context: ApiContext, appId: string, id: string, revision: string, now?: () => number, send?: (params: Parameters<typeof sesSend>[0]) => Promise<import("@aws-sdk/client-ses").SendEmailCommandOutput>, scheduled?: boolean) => Promise<CrmExecutionReceipt>;
|
|
123
|
+
/** Public opaque unsubscribe capability; only updates the exact consent-bound recipient. */
|
|
124
|
+
export declare const unsubscribeCrmExecution: (context: ApiContext, appId: string, token: string, now?: () => number) => Promise<boolean>;
|
|
125
|
+
export {};
|