@iexec/web3mail 1.6.0-nightly-b2a0e93 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/utils/subgraphQuery.js +1 -2
- package/dist/utils/subgraphQuery.js.map +1 -1
- package/dist/utils/validators.d.ts +35 -0
- package/dist/utils/validators.js +45 -1
- package/dist/utils/validators.js.map +1 -1
- package/dist/web3mail/IExecWeb3mail.d.ts +4 -2
- package/dist/web3mail/IExecWeb3mail.js +24 -1
- package/dist/web3mail/IExecWeb3mail.js.map +1 -1
- package/dist/web3mail/fetchMyContacts.js +2 -1
- package/dist/web3mail/fetchMyContacts.js.map +1 -1
- package/dist/web3mail/fetchUserContacts.js +1 -1
- package/dist/web3mail/fetchUserContacts.js.map +1 -1
- package/dist/web3mail/internalTypes.d.ts +4 -4
- package/dist/web3mail/prepareEmailCampaign.d.ts +4 -0
- package/dist/web3mail/prepareEmailCampaign.js +106 -0
- package/dist/web3mail/prepareEmailCampaign.js.map +1 -0
- package/dist/web3mail/sendEmail.d.ts +2 -2
- package/dist/web3mail/sendEmail.js +191 -135
- package/dist/web3mail/sendEmail.js.map +1 -1
- package/dist/web3mail/sendEmailCampaign.d.ts +4 -0
- package/dist/web3mail/sendEmailCampaign.js +43 -0
- package/dist/web3mail/sendEmailCampaign.js.map +1 -0
- package/dist/web3mail/types.d.ts +83 -24
- package/package.json +3 -4
- package/src/utils/subgraphQuery.ts +1 -2
- package/src/utils/validators.ts +68 -1
- package/src/web3mail/IExecWeb3mail.ts +38 -5
- package/src/web3mail/fetchMyContacts.ts +2 -1
- package/src/web3mail/fetchUserContacts.ts +7 -5
- package/src/web3mail/internalTypes.ts +5 -3
- package/src/web3mail/prepareEmailCampaign.ts +170 -0
- package/src/web3mail/sendEmail.ts +246 -150
- package/src/web3mail/sendEmailCampaign.ts +69 -0
- package/src/web3mail/types.ts +88 -26
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
2
|
import {
|
|
3
|
+
CALLBACK_WEB3MAIL,
|
|
3
4
|
DEFAULT_CONTENT_TYPE,
|
|
4
5
|
MAX_DESIRED_APP_ORDER_PRICE,
|
|
5
6
|
MAX_DESIRED_DATA_ORDER_PRICE,
|
|
6
7
|
MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
|
|
7
8
|
} from '../config/config.js';
|
|
8
9
|
import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
|
|
10
|
+
import { generateSecureUniqueId } from '../utils/generateUniqueId.js';
|
|
9
11
|
import * as ipfs from '../utils/ipfs-service.js';
|
|
10
12
|
import { checkProtectedDataValidity } from '../utils/subgraphQuery.js';
|
|
11
13
|
import {
|
|
12
14
|
addressOrEnsSchema,
|
|
15
|
+
addressSchema,
|
|
13
16
|
booleanSchema,
|
|
14
17
|
contentTypeSchema,
|
|
15
18
|
emailContentSchema,
|
|
@@ -19,11 +22,14 @@ import {
|
|
|
19
22
|
senderNameSchema,
|
|
20
23
|
throwIfMissing,
|
|
21
24
|
} from '../utils/validators.js';
|
|
25
|
+
import {
|
|
26
|
+
checkUserVoucher,
|
|
27
|
+
filterWorkerpoolOrders,
|
|
28
|
+
} from './sendEmail.models.js';
|
|
22
29
|
import { SendEmailParams, SendEmailResponse } from './types.js';
|
|
23
30
|
import {
|
|
24
31
|
DappAddressConsumer,
|
|
25
32
|
DappWhitelistAddressConsumer,
|
|
26
|
-
DataProtectorConsumer,
|
|
27
33
|
IExecConsumer,
|
|
28
34
|
IpfsGatewayConfigConsumer,
|
|
29
35
|
IpfsNodeConfigConsumer,
|
|
@@ -35,9 +41,9 @@ export type SendEmail = typeof sendEmail;
|
|
|
35
41
|
export const sendEmail = async ({
|
|
36
42
|
graphQLClient = throwIfMissing(),
|
|
37
43
|
iexec = throwIfMissing(),
|
|
38
|
-
|
|
39
|
-
workerpoolAddressOrEns = throwIfMissing(),
|
|
44
|
+
workerpoolAddressOrEns,
|
|
40
45
|
dappAddressOrENS,
|
|
46
|
+
dappWhitelistAddress,
|
|
41
47
|
ipfsNode,
|
|
42
48
|
ipfsGateway,
|
|
43
49
|
emailSubject,
|
|
@@ -49,8 +55,6 @@ export const sendEmail = async ({
|
|
|
49
55
|
workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
|
|
50
56
|
senderName,
|
|
51
57
|
protectedData,
|
|
52
|
-
grantedAccess,
|
|
53
|
-
maxProtectedDataPerTask,
|
|
54
58
|
useVoucher = false,
|
|
55
59
|
}: IExecConsumer &
|
|
56
60
|
SubgraphConsumer &
|
|
@@ -58,53 +62,203 @@ export const sendEmail = async ({
|
|
|
58
62
|
DappWhitelistAddressConsumer &
|
|
59
63
|
IpfsNodeConfigConsumer &
|
|
60
64
|
IpfsGatewayConfigConsumer &
|
|
61
|
-
SendEmailParams
|
|
62
|
-
|
|
65
|
+
SendEmailParams): Promise<SendEmailResponse> => {
|
|
66
|
+
const vDatasetAddress = addressOrEnsSchema()
|
|
67
|
+
.required()
|
|
68
|
+
.label('protectedData')
|
|
69
|
+
.validateSync(protectedData);
|
|
70
|
+
|
|
71
|
+
const vEmailSubject = emailSubjectSchema()
|
|
72
|
+
.required()
|
|
73
|
+
.label('emailSubject')
|
|
74
|
+
.validateSync(emailSubject);
|
|
75
|
+
|
|
76
|
+
const vEmailContent = emailContentSchema()
|
|
77
|
+
.required()
|
|
78
|
+
.label('emailContent')
|
|
79
|
+
.validateSync(emailContent);
|
|
80
|
+
|
|
81
|
+
const vContentType = contentTypeSchema()
|
|
82
|
+
.required()
|
|
83
|
+
.label('contentType')
|
|
84
|
+
.validateSync(contentType);
|
|
85
|
+
|
|
86
|
+
const vSenderName = senderNameSchema()
|
|
87
|
+
.label('senderName')
|
|
88
|
+
.validateSync(senderName);
|
|
89
|
+
|
|
90
|
+
const vLabel = labelSchema().label('label').validateSync(label);
|
|
91
|
+
|
|
92
|
+
const vWorkerpoolAddressOrEns = addressOrEnsSchema()
|
|
93
|
+
.required()
|
|
94
|
+
.label('WorkerpoolAddressOrEns')
|
|
95
|
+
.validateSync(workerpoolAddressOrEns);
|
|
96
|
+
|
|
97
|
+
const vDappAddressOrENS = addressOrEnsSchema()
|
|
98
|
+
.required()
|
|
99
|
+
.label('dappAddressOrENS')
|
|
100
|
+
.validateSync(dappAddressOrENS);
|
|
101
|
+
|
|
102
|
+
const vDappWhitelistAddress = addressSchema()
|
|
103
|
+
.required()
|
|
104
|
+
.label('dappWhitelistAddress')
|
|
105
|
+
.validateSync(dappWhitelistAddress);
|
|
106
|
+
|
|
107
|
+
const vDataMaxPrice = positiveNumberSchema()
|
|
108
|
+
.label('dataMaxPrice')
|
|
109
|
+
.validateSync(dataMaxPrice);
|
|
110
|
+
|
|
111
|
+
const vAppMaxPrice = positiveNumberSchema()
|
|
112
|
+
.label('appMaxPrice')
|
|
113
|
+
.validateSync(appMaxPrice);
|
|
114
|
+
|
|
115
|
+
const vWorkerpoolMaxPrice = positiveNumberSchema()
|
|
116
|
+
.label('workerpoolMaxPrice')
|
|
117
|
+
.validateSync(workerpoolMaxPrice);
|
|
118
|
+
|
|
119
|
+
const vUseVoucher = booleanSchema()
|
|
120
|
+
.label('useVoucher')
|
|
121
|
+
.validateSync(useVoucher);
|
|
122
|
+
|
|
123
|
+
// Check protected data schema through subgraph
|
|
124
|
+
const isValidProtectedData = await checkProtectedDataValidity(
|
|
125
|
+
graphQLClient,
|
|
126
|
+
vDatasetAddress
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (!isValidProtectedData) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
'This protected data does not contain "email:string" in its schema.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const requesterAddress = await iexec.wallet.getAddress();
|
|
136
|
+
|
|
137
|
+
let userVoucher;
|
|
138
|
+
if (vUseVoucher) {
|
|
139
|
+
try {
|
|
140
|
+
userVoucher = await iexec.voucher.showUserVoucher(requesterAddress);
|
|
141
|
+
checkUserVoucher({ userVoucher });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (err?.message?.startsWith('No Voucher found for address')) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'Oops, it seems your wallet is not associated with any voucher. Check on https://builder.iex.ec/'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
63
152
|
try {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
153
|
+
const [
|
|
154
|
+
datasetorderForApp,
|
|
155
|
+
datasetorderForWhitelist,
|
|
156
|
+
apporder,
|
|
157
|
+
workerpoolorder,
|
|
158
|
+
] = await Promise.all([
|
|
159
|
+
// Fetch dataset order for web3mail app
|
|
160
|
+
iexec.orderbook
|
|
161
|
+
.fetchDatasetOrderbook({
|
|
162
|
+
dataset: vDatasetAddress,
|
|
163
|
+
app: dappAddressOrENS,
|
|
164
|
+
requester: requesterAddress,
|
|
165
|
+
})
|
|
166
|
+
.then((datasetOrderbook) => {
|
|
167
|
+
const desiredPriceDataOrderbook = datasetOrderbook.orders.filter(
|
|
168
|
+
(order) => order.order.datasetprice <= vDataMaxPrice
|
|
169
|
+
);
|
|
170
|
+
return desiredPriceDataOrderbook[0]?.order; // may be undefined
|
|
171
|
+
}),
|
|
172
|
+
|
|
173
|
+
// Fetch dataset order for web3mail whitelist
|
|
174
|
+
iexec.orderbook
|
|
175
|
+
.fetchDatasetOrderbook({
|
|
176
|
+
dataset: vDatasetAddress,
|
|
177
|
+
app: vDappWhitelistAddress,
|
|
178
|
+
requester: requesterAddress,
|
|
179
|
+
})
|
|
180
|
+
.then((datasetOrderbook) => {
|
|
181
|
+
const desiredPriceDataOrderbook = datasetOrderbook.orders.filter(
|
|
182
|
+
(order) => order.order.datasetprice <= vDataMaxPrice
|
|
183
|
+
);
|
|
184
|
+
return desiredPriceDataOrderbook[0]?.order; // may be undefined
|
|
185
|
+
}),
|
|
186
|
+
|
|
187
|
+
// Fetch app order
|
|
188
|
+
iexec.orderbook
|
|
189
|
+
.fetchAppOrderbook({
|
|
190
|
+
app: dappAddressOrENS,
|
|
191
|
+
minTag: ['tee', 'scone'],
|
|
192
|
+
maxTag: ['tee', 'scone'],
|
|
193
|
+
workerpool: workerpoolAddressOrEns,
|
|
194
|
+
})
|
|
195
|
+
.then((appOrderbook) => {
|
|
196
|
+
const desiredPriceAppOrderbook = appOrderbook.orders.filter(
|
|
197
|
+
(order) => order.order.appprice <= vAppMaxPrice
|
|
198
|
+
);
|
|
199
|
+
const desiredPriceAppOrder = desiredPriceAppOrderbook[0]?.order;
|
|
200
|
+
if (!desiredPriceAppOrder) {
|
|
201
|
+
throw new Error('No App order found for the desired price');
|
|
202
|
+
}
|
|
203
|
+
return desiredPriceAppOrder;
|
|
204
|
+
}),
|
|
205
|
+
|
|
206
|
+
// Fetch workerpool order for App or AppWhitelist
|
|
207
|
+
Promise.all([
|
|
208
|
+
// for app
|
|
209
|
+
iexec.orderbook.fetchWorkerpoolOrderbook({
|
|
210
|
+
workerpool: workerpoolAddressOrEns,
|
|
211
|
+
app: vDappAddressOrENS,
|
|
212
|
+
dataset: vDatasetAddress,
|
|
213
|
+
requester: requesterAddress, // public orders + user specific orders
|
|
214
|
+
isRequesterStrict: useVoucher, // If voucher, we only want user specific orders
|
|
215
|
+
minTag: ['tee', 'scone'],
|
|
216
|
+
maxTag: ['tee', 'scone'],
|
|
217
|
+
category: 0,
|
|
218
|
+
}),
|
|
219
|
+
// for app whitelist
|
|
220
|
+
iexec.orderbook.fetchWorkerpoolOrderbook({
|
|
221
|
+
workerpool: workerpoolAddressOrEns,
|
|
222
|
+
app: vDappWhitelistAddress,
|
|
223
|
+
dataset: vDatasetAddress,
|
|
224
|
+
requester: requesterAddress, // public orders + user specific orders
|
|
225
|
+
isRequesterStrict: useVoucher, // If voucher, we only want user specific orders
|
|
226
|
+
minTag: ['tee', 'scone'],
|
|
227
|
+
maxTag: ['tee', 'scone'],
|
|
228
|
+
category: 0,
|
|
229
|
+
}),
|
|
230
|
+
]).then(
|
|
231
|
+
([workerpoolOrderbookForApp, workerpoolOrderbookForAppWhitelist]) => {
|
|
232
|
+
const desiredPriceWorkerpoolOrder = filterWorkerpoolOrders({
|
|
233
|
+
workerpoolOrders: [
|
|
234
|
+
...workerpoolOrderbookForApp.orders,
|
|
235
|
+
...workerpoolOrderbookForAppWhitelist.orders,
|
|
236
|
+
],
|
|
237
|
+
workerpoolMaxPrice: vWorkerpoolMaxPrice,
|
|
238
|
+
useVoucher: vUseVoucher,
|
|
239
|
+
userVoucher,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (!desiredPriceWorkerpoolOrder) {
|
|
243
|
+
throw new Error('No Workerpool order found for the desired price');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return desiredPriceWorkerpoolOrder;
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
if (!workerpoolorder) {
|
|
252
|
+
throw new Error('No Workerpool order found for the desired price');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const datasetorder = datasetorderForApp || datasetorderForWhitelist;
|
|
256
|
+
if (!datasetorder) {
|
|
257
|
+
throw new Error('No Dataset order found for the desired price');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Push requester secrets
|
|
261
|
+
const requesterSecretId = generateSecureUniqueId(16);
|
|
108
262
|
const emailContentEncryptionKey = iexec.dataset.generateEncryptionKey();
|
|
109
263
|
const encryptedFile = await iexec.dataset
|
|
110
264
|
.encrypt(Buffer.from(vEmailContent, 'utf8'), emailContentEncryptionKey)
|
|
@@ -115,11 +269,10 @@ export const sendEmail = async ({
|
|
|
115
269
|
});
|
|
116
270
|
});
|
|
117
271
|
|
|
118
|
-
// Push email message to IPFS
|
|
119
272
|
const cid = await ipfs
|
|
120
273
|
.add(encryptedFile, {
|
|
121
|
-
ipfsNode,
|
|
122
|
-
ipfsGateway,
|
|
274
|
+
ipfsNode: ipfsNode,
|
|
275
|
+
ipfsGateway: ipfsGateway,
|
|
123
276
|
})
|
|
124
277
|
.catch((e) => {
|
|
125
278
|
throw new WorkflowError({
|
|
@@ -127,118 +280,61 @@ export const sendEmail = async ({
|
|
|
127
280
|
errorCause: e,
|
|
128
281
|
});
|
|
129
282
|
});
|
|
283
|
+
|
|
130
284
|
const multiaddr = `/ipfs/${cid}`;
|
|
131
285
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const requesterSecretId = 1;
|
|
136
|
-
const secrets = {
|
|
137
|
-
[requesterSecretId]: JSON.stringify({
|
|
138
|
-
senderName: vSenderName,
|
|
286
|
+
await iexec.secrets.pushRequesterSecret(
|
|
287
|
+
requesterSecretId,
|
|
288
|
+
JSON.stringify({
|
|
139
289
|
emailSubject: vEmailSubject,
|
|
140
290
|
emailContentMultiAddr: multiaddr,
|
|
141
291
|
contentType: vContentType,
|
|
292
|
+
senderName: vSenderName,
|
|
142
293
|
emailContentEncryptionKey,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Bulk processing
|
|
146
|
-
if (grantedAccess) {
|
|
147
|
-
const vMaxProtectedDataPerTask = positiveNumberSchema()
|
|
148
|
-
.label('maxProtectedDataPerTask')
|
|
149
|
-
.validateSync(maxProtectedDataPerTask);
|
|
150
|
-
|
|
151
|
-
const bulkRequest = await dataProtector.prepareBulkRequest({
|
|
152
|
-
app: vDappAddressOrENS,
|
|
153
|
-
appMaxPrice: vAppMaxPrice,
|
|
154
|
-
workerpoolMaxPrice: vWorkerpoolMaxPrice,
|
|
155
|
-
workerpool: vWorkerpoolAddressOrEns,
|
|
156
|
-
args: vLabel,
|
|
157
|
-
inputFiles: [],
|
|
158
|
-
secrets,
|
|
159
|
-
bulkAccesses: grantedAccess,
|
|
160
|
-
maxProtectedDataPerTask: vMaxProtectedDataPerTask,
|
|
161
|
-
});
|
|
162
|
-
const processBulkRequestResponse = await dataProtector.processBulkRequest(
|
|
163
|
-
{
|
|
164
|
-
bulkRequest: bulkRequest.bulkRequest,
|
|
165
|
-
useVoucher: vUseVoucher,
|
|
166
|
-
workerpool: vWorkerpoolAddressOrEns,
|
|
167
|
-
}
|
|
168
|
-
);
|
|
169
|
-
return {
|
|
170
|
-
tasks: processBulkRequestResponse.tasks.map((task) => ({
|
|
171
|
-
taskId: task.taskId,
|
|
172
|
-
dealId: task.dealId,
|
|
173
|
-
bulkIndex: task.bulkIndex,
|
|
174
|
-
})),
|
|
175
|
-
} as unknown as SendEmailResponse<SendEmailParams>;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Single processing mode - protectedData is required
|
|
179
|
-
const vDatasetAddress = addressOrEnsSchema()
|
|
180
|
-
.required()
|
|
181
|
-
.label('protectedData')
|
|
182
|
-
.validateSync(protectedData);
|
|
183
|
-
// Check protected data validity through subgraph
|
|
184
|
-
const isValidProtectedData = await checkProtectedDataValidity(
|
|
185
|
-
graphQLClient,
|
|
186
|
-
vDatasetAddress
|
|
294
|
+
useCallback: true,
|
|
295
|
+
})
|
|
187
296
|
);
|
|
188
|
-
if (!isValidProtectedData) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
'This protected data does not contain "email:string" in its schema.'
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
297
|
|
|
194
|
-
|
|
195
|
-
const result = await dataProtector.processProtectedData({
|
|
196
|
-
defaultWorkerpool: vWorkerpoolAddressOrEns,
|
|
197
|
-
protectedData: vDatasetAddress,
|
|
298
|
+
const requestorderToSign = await iexec.order.createRequestorder({
|
|
198
299
|
app: vDappAddressOrENS,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
300
|
+
category: workerpoolorder.category,
|
|
301
|
+
dataset: vDatasetAddress,
|
|
302
|
+
datasetmaxprice: datasetorder.datasetprice,
|
|
303
|
+
appmaxprice: apporder.appprice,
|
|
304
|
+
workerpoolmaxprice: workerpoolorder.workerpoolprice,
|
|
305
|
+
tag: ['tee', 'scone'],
|
|
203
306
|
workerpool: vWorkerpoolAddressOrEns,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
307
|
+
callback: CALLBACK_WEB3MAIL,
|
|
308
|
+
params: {
|
|
309
|
+
iexec_secrets: {
|
|
310
|
+
1: requesterSecretId,
|
|
311
|
+
},
|
|
312
|
+
iexec_args: vLabel,
|
|
313
|
+
},
|
|
209
314
|
});
|
|
210
315
|
|
|
316
|
+
const requestorder = await iexec.order.signRequestorder(requestorderToSign);
|
|
317
|
+
|
|
318
|
+
// Match orders and compute task ID
|
|
319
|
+
const { dealid: dealId } = await iexec.order.matchOrders(
|
|
320
|
+
{
|
|
321
|
+
apporder: apporder,
|
|
322
|
+
datasetorder: datasetorder,
|
|
323
|
+
workerpoolorder: workerpoolorder,
|
|
324
|
+
requestorder: requestorder,
|
|
325
|
+
},
|
|
326
|
+
{ useVoucher: vUseVoucher }
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const taskId = await iexec.deal.computeTaskId(dealId, 0);
|
|
330
|
+
|
|
211
331
|
return {
|
|
212
|
-
taskId
|
|
213
|
-
|
|
332
|
+
taskId,
|
|
333
|
+
dealId,
|
|
334
|
+
};
|
|
214
335
|
} catch (error) {
|
|
215
|
-
// Protocol error detected, re-throwing as-is
|
|
216
|
-
if ((error as any)?.isProtocolError === true) {
|
|
217
|
-
throw error;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Handle protocol errors - this will throw if it's an ApiCallError
|
|
221
|
-
// handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true
|
|
222
336
|
handleIfProtocolError(error);
|
|
223
337
|
|
|
224
|
-
// If we reach here, it's not a protocol error
|
|
225
|
-
// Check if it's a WorkflowError from processProtectedData by checking the message
|
|
226
|
-
const isProcessProtectedDataError =
|
|
227
|
-
error instanceof Error &&
|
|
228
|
-
error.message === 'Failed to process protected data';
|
|
229
|
-
|
|
230
|
-
if (isProcessProtectedDataError) {
|
|
231
|
-
const cause = (error as any)?.cause;
|
|
232
|
-
// Return unwrapped cause (the actual Error object)
|
|
233
|
-
// error.cause should be an Error, but ensure it is
|
|
234
|
-
const unwrappedCause = cause instanceof Error ? cause : error;
|
|
235
|
-
throw new WorkflowError({
|
|
236
|
-
message: 'Failed to sendEmail',
|
|
237
|
-
errorCause: unwrappedCause,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// For all other errors
|
|
242
338
|
throw new WorkflowError({
|
|
243
339
|
message: 'Failed to sendEmail',
|
|
244
340
|
errorCause: error,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NULL_ADDRESS } from 'iexec/utils';
|
|
2
|
+
import { ValidationError } from 'yup';
|
|
3
|
+
import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
|
|
4
|
+
import {
|
|
5
|
+
addressOrEnsSchema,
|
|
6
|
+
campaignRequestSchema,
|
|
7
|
+
throwIfMissing,
|
|
8
|
+
} from '../utils/validators.js';
|
|
9
|
+
import {
|
|
10
|
+
CampaignRequest,
|
|
11
|
+
SendEmailCampaignParams,
|
|
12
|
+
SendEmailCampaignResponse,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
import { DataProtectorConsumer } from './internalTypes.js';
|
|
15
|
+
|
|
16
|
+
export type SendEmailCampaign = typeof sendEmailCampaign;
|
|
17
|
+
|
|
18
|
+
export const sendEmailCampaign = async ({
|
|
19
|
+
dataProtector = throwIfMissing(),
|
|
20
|
+
workerpoolAddressOrEns = throwIfMissing(),
|
|
21
|
+
campaignRequest,
|
|
22
|
+
}: DataProtectorConsumer &
|
|
23
|
+
SendEmailCampaignParams): Promise<SendEmailCampaignResponse> => {
|
|
24
|
+
const vCampaignRequest = campaignRequestSchema()
|
|
25
|
+
.required()
|
|
26
|
+
.label('campaignRequest')
|
|
27
|
+
.validateSync(campaignRequest) as CampaignRequest;
|
|
28
|
+
|
|
29
|
+
const vWorkerpoolAddressOrEns = addressOrEnsSchema()
|
|
30
|
+
.required()
|
|
31
|
+
.label('workerpoolAddressOrEns')
|
|
32
|
+
.validateSync(workerpoolAddressOrEns);
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
vCampaignRequest.workerpool !== NULL_ADDRESS &&
|
|
36
|
+
vCampaignRequest.workerpool.toLowerCase() !==
|
|
37
|
+
vWorkerpoolAddressOrEns.toLowerCase()
|
|
38
|
+
) {
|
|
39
|
+
throw new ValidationError(
|
|
40
|
+
"workerpoolAddressOrEns doesn't match campaignRequest workerpool"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Process the prepared bulk request
|
|
46
|
+
const processBulkRequestResponse = await dataProtector.processBulkRequest({
|
|
47
|
+
bulkRequest: vCampaignRequest,
|
|
48
|
+
workerpool: vWorkerpoolAddressOrEns,
|
|
49
|
+
waitForResult: false,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return processBulkRequestResponse;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Protocol error detected, re-throwing as-is
|
|
55
|
+
if ((error as any)?.isProtocolError === true) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle protocol errors - this will throw if it's an ApiCallError
|
|
60
|
+
// handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true
|
|
61
|
+
handleIfProtocolError(error);
|
|
62
|
+
|
|
63
|
+
// For all other errors
|
|
64
|
+
throw new WorkflowError({
|
|
65
|
+
message: 'Failed to sendEmailCampaign',
|
|
66
|
+
errorCause: error,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
package/src/web3mail/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EnhancedWallet } from 'iexec';
|
|
2
2
|
import { IExecConfigOptions } from 'iexec/IExecConfig';
|
|
3
|
+
import type { BulkRequest } from '@iexec/dataprotector';
|
|
3
4
|
|
|
4
5
|
export type Web3SignerProvider = EnhancedWallet;
|
|
5
6
|
|
|
@@ -11,6 +12,22 @@ export type Address = string;
|
|
|
11
12
|
|
|
12
13
|
export type TimeStamp = string;
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* request to send email in bulk
|
|
17
|
+
*
|
|
18
|
+
* use `prepareEmailCampaign()` to create a `CampaignRequest`
|
|
19
|
+
*
|
|
20
|
+
* then use `sendEmailCampaign()` to send the campaign
|
|
21
|
+
*/
|
|
22
|
+
export type CampaignRequest = BulkRequest;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* authorization signed by the data owner granting access to this contact
|
|
26
|
+
*
|
|
27
|
+
* `GrantedAccess` are obtained by fetching contacts (e.g. `fetchMyContacts()` or `fetchUserContacts()`)
|
|
28
|
+
*
|
|
29
|
+
* `GrantedAccess` can be consumed for email campaigns (e.g. `prepareEmailCampaign()` then `sendEmailCampaign()`)
|
|
30
|
+
*/
|
|
14
31
|
export type GrantedAccess = {
|
|
15
32
|
dataset: string;
|
|
16
33
|
datasetprice: string;
|
|
@@ -38,14 +55,7 @@ export type Contact = {
|
|
|
38
55
|
export type SendEmailParams = {
|
|
39
56
|
emailSubject: string;
|
|
40
57
|
emailContent: string;
|
|
41
|
-
protectedData
|
|
42
|
-
/**
|
|
43
|
-
* Granted access to process.
|
|
44
|
-
* use prepareBulkRequest of dataprotector to create a bulk request.
|
|
45
|
-
* if not provided, the single message will be processed.
|
|
46
|
-
*/
|
|
47
|
-
grantedAccess?: GrantedAccess[];
|
|
48
|
-
maxProtectedDataPerTask?: number;
|
|
58
|
+
protectedData: Address;
|
|
49
59
|
contentType?: string;
|
|
50
60
|
senderName?: string;
|
|
51
61
|
label?: string;
|
|
@@ -61,6 +71,9 @@ export type FetchMyContactsParams = {
|
|
|
61
71
|
* Get contacts for this specific user only
|
|
62
72
|
*/
|
|
63
73
|
isUserStrict?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* If true, returns only contacts with bulk processing access grants
|
|
76
|
+
*/
|
|
64
77
|
bulkOnly?: boolean;
|
|
65
78
|
};
|
|
66
79
|
|
|
@@ -71,27 +84,17 @@ export type FetchUserContactsParams = {
|
|
|
71
84
|
userAddress: Address;
|
|
72
85
|
} & FetchMyContactsParams;
|
|
73
86
|
|
|
74
|
-
type
|
|
87
|
+
export type SendEmailResponse = {
|
|
88
|
+
/**
|
|
89
|
+
* ID of the task
|
|
90
|
+
*/
|
|
75
91
|
taskId: string;
|
|
92
|
+
/**
|
|
93
|
+
* ID of the deal containing the task
|
|
94
|
+
*/
|
|
95
|
+
dealId: string;
|
|
76
96
|
};
|
|
77
97
|
|
|
78
|
-
type SendEmailBulkResponse = {
|
|
79
|
-
tasks: {
|
|
80
|
-
bulkIndex: number;
|
|
81
|
-
taskId: string;
|
|
82
|
-
dealId: string;
|
|
83
|
-
}[];
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export type SendEmailResponse<Params = { protectedData: Address }> =
|
|
87
|
-
Params extends {
|
|
88
|
-
grantedAccess: GrantedAccess[];
|
|
89
|
-
}
|
|
90
|
-
? SendEmailBulkResponse
|
|
91
|
-
: never & Params extends { protectedData: Address }
|
|
92
|
-
? SendEmailSingleResponse
|
|
93
|
-
: never;
|
|
94
|
-
|
|
95
98
|
/**
|
|
96
99
|
* Configuration options for Web3Mail.
|
|
97
100
|
*/
|
|
@@ -139,3 +142,62 @@ export type Web3MailConfigOptions = {
|
|
|
139
142
|
*/
|
|
140
143
|
allowExperimentalNetworks?: boolean;
|
|
141
144
|
};
|
|
145
|
+
|
|
146
|
+
export type PrepareEmailCampaignParams = {
|
|
147
|
+
/**
|
|
148
|
+
* List of `GrantedAccess` to contacts to send emails to in bulk.
|
|
149
|
+
*
|
|
150
|
+
* use `fetchMyContacts({ bulkOnly: true })` to get granted accesses.
|
|
151
|
+
*/
|
|
152
|
+
grantedAccesses: GrantedAccess[];
|
|
153
|
+
maxProtectedDataPerTask?: number;
|
|
154
|
+
senderName?: string;
|
|
155
|
+
emailSubject: string;
|
|
156
|
+
emailContent: string;
|
|
157
|
+
contentType?: string;
|
|
158
|
+
label?: string;
|
|
159
|
+
workerpoolAddressOrEns?: AddressOrENS;
|
|
160
|
+
dataMaxPrice?: number;
|
|
161
|
+
appMaxPrice?: number;
|
|
162
|
+
workerpoolMaxPrice?: number;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type PrepareEmailCampaignResponse = {
|
|
166
|
+
/**
|
|
167
|
+
* The prepared campaign request
|
|
168
|
+
*
|
|
169
|
+
* Use this in `sendEmailCampaign()` to start or continue sending the campaign
|
|
170
|
+
*/
|
|
171
|
+
campaignRequest: CampaignRequest;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export type SendEmailCampaignParams = {
|
|
175
|
+
/**
|
|
176
|
+
* The prepared campaign request from `prepareEmailCampaign()`
|
|
177
|
+
*/
|
|
178
|
+
campaignRequest: CampaignRequest;
|
|
179
|
+
/**
|
|
180
|
+
* Workerpool address or ENS to use for processing
|
|
181
|
+
*/
|
|
182
|
+
workerpoolAddressOrEns?: AddressOrENS;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export type SendEmailCampaignResponse = {
|
|
186
|
+
/**
|
|
187
|
+
* List of tasks created for the campaign
|
|
188
|
+
*/
|
|
189
|
+
tasks: Array<{
|
|
190
|
+
/**
|
|
191
|
+
* ID of the task
|
|
192
|
+
*/
|
|
193
|
+
taskId: string;
|
|
194
|
+
/**
|
|
195
|
+
* ID of the deal containing the task
|
|
196
|
+
*/
|
|
197
|
+
dealId: string;
|
|
198
|
+
/**
|
|
199
|
+
* Index of the task in the bulk request
|
|
200
|
+
*/
|
|
201
|
+
bulkIndex: number;
|
|
202
|
+
}>;
|
|
203
|
+
};
|