@iexec/web3mail 1.5.0 → 1.6.0-nightly-8729cd2
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/config/config.js +0 -1
- package/dist/config/config.js.map +1 -1
- package/dist/utils/subgraphQuery.js +6 -2
- package/dist/utils/subgraphQuery.js.map +1 -1
- package/dist/web3mail/IExecWeb3mail.d.ts +4 -2
- package/dist/web3mail/IExecWeb3mail.js +15 -0
- package/dist/web3mail/IExecWeb3mail.js.map +1 -1
- package/dist/web3mail/fetchMyContacts.d.ts +1 -1
- package/dist/web3mail/fetchMyContacts.js +2 -1
- package/dist/web3mail/fetchMyContacts.js.map +1 -1
- package/dist/web3mail/fetchUserContacts.d.ts +1 -1
- package/dist/web3mail/fetchUserContacts.js +20 -3
- package/dist/web3mail/fetchUserContacts.js.map +1 -1
- package/dist/web3mail/internalTypes.d.ts +4 -0
- package/dist/web3mail/sendEmail.d.ts +4 -3
- package/dist/web3mail/sendEmail.js +129 -187
- package/dist/web3mail/sendEmail.js.map +1 -1
- package/dist/web3mail/types.d.ts +24 -3
- package/package.json +3 -3
- package/src/config/config.ts +0 -1
- package/src/utils/subgraphQuery.ts +18 -14
- package/src/web3mail/IExecWeb3mail.ts +25 -2
- package/src/web3mail/fetchMyContacts.ts +2 -0
- package/src/web3mail/fetchUserContacts.ts +30 -15
- package/src/web3mail/internalTypes.ts +3 -0
- package/src/web3mail/sendEmail.ts +145 -219
- package/src/web3mail/types.ts +25 -3
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isEnsTest,
|
|
13
13
|
throwIfMissing,
|
|
14
14
|
} from '../utils/validators.js';
|
|
15
|
-
import { Contact, FetchUserContactsParams } from './types.js';
|
|
15
|
+
import { Contact, FetchUserContactsParams, GrantedAccess } from './types.js';
|
|
16
16
|
import {
|
|
17
17
|
DappAddressConsumer,
|
|
18
18
|
DappWhitelistAddressConsumer,
|
|
@@ -27,6 +27,7 @@ export const fetchUserContacts = async ({
|
|
|
27
27
|
dappWhitelistAddress = throwIfMissing(),
|
|
28
28
|
userAddress,
|
|
29
29
|
isUserStrict = false,
|
|
30
|
+
bulkOnly = false,
|
|
30
31
|
}: IExecConsumer &
|
|
31
32
|
SubgraphConsumer &
|
|
32
33
|
DappAddressConsumer &
|
|
@@ -47,6 +48,7 @@ export const fetchUserContacts = async ({
|
|
|
47
48
|
const vIsUserStrict = booleanSchema()
|
|
48
49
|
.label('isUserStrict')
|
|
49
50
|
.validateSync(isUserStrict);
|
|
51
|
+
const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly);
|
|
50
52
|
|
|
51
53
|
try {
|
|
52
54
|
const [dappOrders, whitelistOrders] = await Promise.all([
|
|
@@ -55,15 +57,16 @@ export const fetchUserContacts = async ({
|
|
|
55
57
|
userAddress: vUserAddress,
|
|
56
58
|
appAddress: vDappAddressOrENS,
|
|
57
59
|
isUserStrict: vIsUserStrict,
|
|
60
|
+
bulkOnly: vBulkOnly,
|
|
58
61
|
}),
|
|
59
62
|
fetchAllOrdersByApp({
|
|
60
63
|
iexec,
|
|
61
64
|
userAddress: vUserAddress,
|
|
62
65
|
appAddress: vDappWhitelistAddress,
|
|
63
66
|
isUserStrict: vIsUserStrict,
|
|
67
|
+
bulkOnly: vBulkOnly,
|
|
64
68
|
}),
|
|
65
69
|
]);
|
|
66
|
-
|
|
67
70
|
const orders = dappOrders.concat(whitelistOrders);
|
|
68
71
|
const myContacts: Omit<Contact, 'name'>[] = [];
|
|
69
72
|
let web3DappResolvedAddress = vDappAddressOrENS;
|
|
@@ -77,13 +80,25 @@ export const fetchUserContacts = async ({
|
|
|
77
80
|
order.order.apprestrict.toLowerCase() ===
|
|
78
81
|
vDappWhitelistAddress.toLowerCase()
|
|
79
82
|
) {
|
|
80
|
-
const contact = {
|
|
83
|
+
const contact: Contact = {
|
|
81
84
|
address: order.order.dataset.toLowerCase(),
|
|
82
85
|
owner: order.signer.toLowerCase(),
|
|
83
86
|
remainingAccess: order.remaining,
|
|
84
87
|
accessPrice: order.order.datasetprice,
|
|
85
88
|
accessGrantTimestamp: order.publicationTimestamp,
|
|
86
89
|
isUserStrict: order.order.requesterrestrict !== ZeroAddress,
|
|
90
|
+
grantedAccess: {
|
|
91
|
+
dataset: order.order.dataset,
|
|
92
|
+
datasetprice: order.order.datasetprice.toString(),
|
|
93
|
+
volume: order.order.volume.toString(),
|
|
94
|
+
tag: order.order.tag,
|
|
95
|
+
apprestrict: order.order.apprestrict,
|
|
96
|
+
workerpoolrestrict: order.order.workerpoolrestrict,
|
|
97
|
+
requesterrestrict: order.order.requesterrestrict,
|
|
98
|
+
salt: order.order.salt,
|
|
99
|
+
sign: order.order.sign,
|
|
100
|
+
remainingAccess: order.remaining,
|
|
101
|
+
} as GrantedAccess,
|
|
87
102
|
};
|
|
88
103
|
myContacts.push(contact);
|
|
89
104
|
}
|
|
@@ -94,7 +109,6 @@ export const fetchUserContacts = async ({
|
|
|
94
109
|
return await getValidContact(graphQLClient, myContacts);
|
|
95
110
|
} catch (error) {
|
|
96
111
|
handleIfProtocolError(error);
|
|
97
|
-
|
|
98
112
|
throw new WorkflowError({
|
|
99
113
|
message: 'Failed to fetch user contacts',
|
|
100
114
|
errorCause: error,
|
|
@@ -107,23 +121,24 @@ async function fetchAllOrdersByApp({
|
|
|
107
121
|
userAddress,
|
|
108
122
|
appAddress,
|
|
109
123
|
isUserStrict,
|
|
124
|
+
bulkOnly,
|
|
110
125
|
}: {
|
|
111
126
|
iexec: IExec;
|
|
112
127
|
userAddress: string;
|
|
113
128
|
appAddress: string;
|
|
114
129
|
isUserStrict: boolean;
|
|
130
|
+
bulkOnly?: boolean;
|
|
115
131
|
}): Promise<PublishedDatasetorder[]> {
|
|
116
|
-
const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook(
|
|
117
|
-
ANY_DATASET_ADDRESS,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
132
|
+
const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook({
|
|
133
|
+
dataset: ANY_DATASET_ADDRESS,
|
|
134
|
+
app: appAddress,
|
|
135
|
+
requester: userAddress,
|
|
136
|
+
isAppStrict: true,
|
|
137
|
+
isRequesterStrict: isUserStrict,
|
|
138
|
+
bulkOnly,
|
|
139
|
+
// Use maxPageSize here to avoid too many round-trips (we want everything anyway)
|
|
140
|
+
pageSize: 1000,
|
|
141
|
+
});
|
|
127
142
|
const { orders: allOrders } = await autoPaginateRequest({
|
|
128
143
|
request: ordersFirstPage,
|
|
129
144
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IExec } from 'iexec';
|
|
2
2
|
import { AddressOrENS } from './types.js';
|
|
3
3
|
import { GraphQLClient } from 'graphql-request';
|
|
4
|
+
import { IExecDataProtectorCore } from '@iexec/dataprotector';
|
|
4
5
|
|
|
5
6
|
export type ProtectedDataQuery = {
|
|
6
7
|
id: string;
|
|
@@ -31,6 +32,8 @@ export type IExecConsumer = {
|
|
|
31
32
|
iexec: IExec;
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
export type DataProtectorConsumer = { dataProtector: IExecDataProtectorCore };
|
|
36
|
+
|
|
34
37
|
export type SubgraphConsumer = {
|
|
35
38
|
graphQLClient: GraphQLClient;
|
|
36
39
|
};
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
+
import { ProcessBulkRequestResponse } from '@iexec/dataprotector';
|
|
2
3
|
import {
|
|
3
|
-
CALLBACK_WEB3MAIL,
|
|
4
4
|
DEFAULT_CONTENT_TYPE,
|
|
5
5
|
MAX_DESIRED_APP_ORDER_PRICE,
|
|
6
6
|
MAX_DESIRED_DATA_ORDER_PRICE,
|
|
7
7
|
MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
|
|
8
8
|
} from '../config/config.js';
|
|
9
9
|
import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
|
|
10
|
-
import { generateSecureUniqueId } from '../utils/generateUniqueId.js';
|
|
11
10
|
import * as ipfs from '../utils/ipfs-service.js';
|
|
12
11
|
import { checkProtectedDataValidity } from '../utils/subgraphQuery.js';
|
|
13
12
|
import {
|
|
14
13
|
addressOrEnsSchema,
|
|
15
|
-
addressSchema,
|
|
16
14
|
booleanSchema,
|
|
17
15
|
contentTypeSchema,
|
|
18
16
|
emailContentSchema,
|
|
@@ -22,14 +20,11 @@ import {
|
|
|
22
20
|
senderNameSchema,
|
|
23
21
|
throwIfMissing,
|
|
24
22
|
} from '../utils/validators.js';
|
|
25
|
-
import {
|
|
26
|
-
checkUserVoucher,
|
|
27
|
-
filterWorkerpoolOrders,
|
|
28
|
-
} from './sendEmail.models.js';
|
|
29
|
-
import { SendEmailParams, SendEmailResponse } from './types.js';
|
|
23
|
+
import { SendEmailParams, SendEmailSingleResponse } from './types.js';
|
|
30
24
|
import {
|
|
31
25
|
DappAddressConsumer,
|
|
32
26
|
DappWhitelistAddressConsumer,
|
|
27
|
+
DataProtectorConsumer,
|
|
33
28
|
IExecConsumer,
|
|
34
29
|
IpfsGatewayConfigConsumer,
|
|
35
30
|
IpfsNodeConfigConsumer,
|
|
@@ -41,9 +36,9 @@ export type SendEmail = typeof sendEmail;
|
|
|
41
36
|
export const sendEmail = async ({
|
|
42
37
|
graphQLClient = throwIfMissing(),
|
|
43
38
|
iexec = throwIfMissing(),
|
|
44
|
-
|
|
39
|
+
dataProtector = throwIfMissing(),
|
|
40
|
+
workerpoolAddressOrEns = throwIfMissing(),
|
|
45
41
|
dappAddressOrENS,
|
|
46
|
-
dappWhitelistAddress,
|
|
47
42
|
ipfsNode,
|
|
48
43
|
ipfsGateway,
|
|
49
44
|
emailSubject,
|
|
@@ -55,6 +50,8 @@ export const sendEmail = async ({
|
|
|
55
50
|
workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
|
|
56
51
|
senderName,
|
|
57
52
|
protectedData,
|
|
53
|
+
grantedAccess,
|
|
54
|
+
maxProtectedDataPerTask,
|
|
58
55
|
useVoucher = false,
|
|
59
56
|
}: IExecConsumer &
|
|
60
57
|
SubgraphConsumer &
|
|
@@ -62,182 +59,55 @@ export const sendEmail = async ({
|
|
|
62
59
|
DappWhitelistAddressConsumer &
|
|
63
60
|
IpfsNodeConfigConsumer &
|
|
64
61
|
IpfsGatewayConfigConsumer &
|
|
65
|
-
SendEmailParams
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.validateSync(protectedData);
|
|
70
|
-
const vEmailSubject = emailSubjectSchema()
|
|
71
|
-
.required()
|
|
72
|
-
.label('emailSubject')
|
|
73
|
-
.validateSync(emailSubject);
|
|
74
|
-
const vEmailContent = emailContentSchema()
|
|
75
|
-
.required()
|
|
76
|
-
.label('emailContent')
|
|
77
|
-
.validateSync(emailContent);
|
|
78
|
-
const vContentType = contentTypeSchema()
|
|
79
|
-
.required()
|
|
80
|
-
.label('contentType')
|
|
81
|
-
.validateSync(contentType);
|
|
82
|
-
const vSenderName = senderNameSchema()
|
|
83
|
-
.label('senderName')
|
|
84
|
-
.validateSync(senderName);
|
|
85
|
-
const vLabel = labelSchema().label('label').validateSync(label);
|
|
86
|
-
const vWorkerpoolAddressOrEns = addressOrEnsSchema()
|
|
87
|
-
.required()
|
|
88
|
-
.label('WorkerpoolAddressOrEns')
|
|
89
|
-
.validateSync(workerpoolAddressOrEns);
|
|
90
|
-
const vDappAddressOrENS = addressOrEnsSchema()
|
|
91
|
-
.required()
|
|
92
|
-
.label('dappAddressOrENS')
|
|
93
|
-
.validateSync(dappAddressOrENS);
|
|
94
|
-
const vDappWhitelistAddress = addressSchema()
|
|
95
|
-
.required()
|
|
96
|
-
.label('dappWhitelistAddress')
|
|
97
|
-
.validateSync(dappWhitelistAddress);
|
|
98
|
-
const vDataMaxPrice = positiveNumberSchema()
|
|
99
|
-
.label('dataMaxPrice')
|
|
100
|
-
.validateSync(dataMaxPrice);
|
|
101
|
-
const vAppMaxPrice = positiveNumberSchema()
|
|
102
|
-
.label('appMaxPrice')
|
|
103
|
-
.validateSync(appMaxPrice);
|
|
104
|
-
const vWorkerpoolMaxPrice = positiveNumberSchema()
|
|
105
|
-
.label('workerpoolMaxPrice')
|
|
106
|
-
.validateSync(workerpoolMaxPrice);
|
|
107
|
-
const vUseVoucher = booleanSchema()
|
|
108
|
-
.label('useVoucher')
|
|
109
|
-
.validateSync(useVoucher);
|
|
110
|
-
|
|
111
|
-
// Check protected data schema through subgraph
|
|
112
|
-
const isValidProtectedData = await checkProtectedDataValidity(
|
|
113
|
-
graphQLClient,
|
|
114
|
-
vDatasetAddress
|
|
115
|
-
);
|
|
116
|
-
if (!isValidProtectedData) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
'This protected data does not contain "email:string" in its schema.'
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const requesterAddress = await iexec.wallet.getAddress();
|
|
123
|
-
|
|
124
|
-
let userVoucher;
|
|
125
|
-
if (vUseVoucher) {
|
|
126
|
-
try {
|
|
127
|
-
userVoucher = await iexec.voucher.showUserVoucher(requesterAddress);
|
|
128
|
-
checkUserVoucher({ userVoucher });
|
|
129
|
-
} catch (err) {
|
|
130
|
-
if (err?.message?.startsWith('No Voucher found for address')) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
'Oops, it seems your wallet is not associated with any voucher. Check on https://builder.iex.ec/'
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
throw err;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
62
|
+
SendEmailParams &
|
|
63
|
+
DataProtectorConsumer): Promise<
|
|
64
|
+
ProcessBulkRequestResponse | SendEmailSingleResponse
|
|
65
|
+
> => {
|
|
139
66
|
try {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
})
|
|
164
|
-
.then((datasetOrderbook) => {
|
|
165
|
-
const desiredPriceDataOrderbook = datasetOrderbook.orders.filter(
|
|
166
|
-
(order) => order.order.datasetprice <= vDataMaxPrice
|
|
167
|
-
);
|
|
168
|
-
return desiredPriceDataOrderbook[0]?.order; // may be undefined
|
|
169
|
-
}),
|
|
170
|
-
// Fetch app order
|
|
171
|
-
iexec.orderbook
|
|
172
|
-
.fetchAppOrderbook(dappAddressOrENS, {
|
|
173
|
-
minTag: ['tee', 'scone'],
|
|
174
|
-
maxTag: ['tee', 'scone'],
|
|
175
|
-
workerpool: workerpoolAddressOrEns,
|
|
176
|
-
})
|
|
177
|
-
.then((appOrderbook) => {
|
|
178
|
-
const desiredPriceAppOrderbook = appOrderbook.orders.filter(
|
|
179
|
-
(order) => order.order.appprice <= vAppMaxPrice
|
|
180
|
-
);
|
|
181
|
-
const desiredPriceAppOrder = desiredPriceAppOrderbook[0]?.order;
|
|
182
|
-
if (!desiredPriceAppOrder) {
|
|
183
|
-
throw new Error('No App order found for the desired price');
|
|
184
|
-
}
|
|
185
|
-
return desiredPriceAppOrder;
|
|
186
|
-
}),
|
|
187
|
-
// Fetch workerpool order for App or AppWhitelist
|
|
188
|
-
Promise.all([
|
|
189
|
-
// for app
|
|
190
|
-
iexec.orderbook.fetchWorkerpoolOrderbook({
|
|
191
|
-
workerpool: workerpoolAddressOrEns,
|
|
192
|
-
app: vDappAddressOrENS,
|
|
193
|
-
dataset: vDatasetAddress,
|
|
194
|
-
requester: requesterAddress, // public orders + user specific orders
|
|
195
|
-
isRequesterStrict: useVoucher, // If voucher, we only want user specific orders
|
|
196
|
-
minTag: ['tee', 'scone'],
|
|
197
|
-
maxTag: ['tee', 'scone'],
|
|
198
|
-
category: 0,
|
|
199
|
-
}),
|
|
200
|
-
// for app whitelist
|
|
201
|
-
iexec.orderbook.fetchWorkerpoolOrderbook({
|
|
202
|
-
workerpool: workerpoolAddressOrEns,
|
|
203
|
-
app: vDappWhitelistAddress,
|
|
204
|
-
dataset: vDatasetAddress,
|
|
205
|
-
requester: requesterAddress, // public orders + user specific orders
|
|
206
|
-
isRequesterStrict: useVoucher, // If voucher, we only want user specific orders
|
|
207
|
-
minTag: ['tee', 'scone'],
|
|
208
|
-
maxTag: ['tee', 'scone'],
|
|
209
|
-
category: 0,
|
|
210
|
-
}),
|
|
211
|
-
]).then(
|
|
212
|
-
([workerpoolOrderbookForApp, workerpoolOrderbookForAppWhitelist]) => {
|
|
213
|
-
const desiredPriceWorkerpoolOrder = filterWorkerpoolOrders({
|
|
214
|
-
workerpoolOrders: [
|
|
215
|
-
...workerpoolOrderbookForApp.orders,
|
|
216
|
-
...workerpoolOrderbookForAppWhitelist.orders,
|
|
217
|
-
],
|
|
218
|
-
workerpoolMaxPrice: vWorkerpoolMaxPrice,
|
|
219
|
-
useVoucher: vUseVoucher,
|
|
220
|
-
userVoucher,
|
|
221
|
-
});
|
|
222
|
-
if (!desiredPriceWorkerpoolOrder) {
|
|
223
|
-
throw new Error('No Workerpool order found for the desired price');
|
|
224
|
-
}
|
|
225
|
-
return desiredPriceWorkerpoolOrder;
|
|
226
|
-
}
|
|
227
|
-
),
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
|
-
if (!workerpoolorder) {
|
|
231
|
-
throw new Error('No Workerpool order found for the desired price');
|
|
232
|
-
}
|
|
67
|
+
const vUseVoucher = booleanSchema()
|
|
68
|
+
.label('useVoucher')
|
|
69
|
+
.validateSync(useVoucher);
|
|
70
|
+
const vWorkerpoolAddressOrEns = addressOrEnsSchema()
|
|
71
|
+
.required()
|
|
72
|
+
.label('WorkerpoolAddressOrEns')
|
|
73
|
+
.validateSync(workerpoolAddressOrEns);
|
|
74
|
+
const vSenderName = senderNameSchema()
|
|
75
|
+
.label('senderName')
|
|
76
|
+
.validateSync(senderName);
|
|
77
|
+
const vEmailSubject = emailSubjectSchema()
|
|
78
|
+
.required()
|
|
79
|
+
.label('emailSubject')
|
|
80
|
+
.validateSync(emailSubject);
|
|
81
|
+
const vEmailContent = emailContentSchema()
|
|
82
|
+
.required()
|
|
83
|
+
.label('emailContent')
|
|
84
|
+
.validateSync(emailContent);
|
|
85
|
+
const vContentType = contentTypeSchema()
|
|
86
|
+
.required()
|
|
87
|
+
.label('contentType')
|
|
88
|
+
.validateSync(contentType);
|
|
89
|
+
const vLabel = labelSchema().label('label').validateSync(label);
|
|
233
90
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
91
|
+
const vDappAddressOrENS = addressOrEnsSchema()
|
|
92
|
+
.required()
|
|
93
|
+
.label('dappAddressOrENS')
|
|
94
|
+
.validateSync(dappAddressOrENS);
|
|
95
|
+
// TODO: remove this once we have a way to pass appWhitelist to processProtectedData function
|
|
96
|
+
// const vDappWhitelistAddress = addressSchema()
|
|
97
|
+
// .required()
|
|
98
|
+
// .label('dappWhitelistAddress')
|
|
99
|
+
// .validateSync(dappWhitelistAddress);
|
|
100
|
+
const vDataMaxPrice = positiveNumberSchema()
|
|
101
|
+
.label('dataMaxPrice')
|
|
102
|
+
.validateSync(dataMaxPrice);
|
|
103
|
+
const vAppMaxPrice = positiveNumberSchema()
|
|
104
|
+
.label('appMaxPrice')
|
|
105
|
+
.validateSync(appMaxPrice);
|
|
106
|
+
const vWorkerpoolMaxPrice = positiveNumberSchema()
|
|
107
|
+
.label('workerpoolMaxPrice')
|
|
108
|
+
.validateSync(workerpoolMaxPrice);
|
|
238
109
|
|
|
239
|
-
//
|
|
240
|
-
const requesterSecretId = generateSecureUniqueId(16);
|
|
110
|
+
// Encrypt email content
|
|
241
111
|
const emailContentEncryptionKey = iexec.dataset.generateEncryptionKey();
|
|
242
112
|
const encryptedFile = await iexec.dataset
|
|
243
113
|
.encrypt(Buffer.from(vEmailContent, 'utf8'), emailContentEncryptionKey)
|
|
@@ -247,10 +117,12 @@ export const sendEmail = async ({
|
|
|
247
117
|
errorCause: e,
|
|
248
118
|
});
|
|
249
119
|
});
|
|
120
|
+
|
|
121
|
+
// Push email message to IPFS
|
|
250
122
|
const cid = await ipfs
|
|
251
123
|
.add(encryptedFile, {
|
|
252
|
-
ipfsNode
|
|
253
|
-
ipfsGateway
|
|
124
|
+
ipfsNode,
|
|
125
|
+
ipfsGateway,
|
|
254
126
|
})
|
|
255
127
|
.catch((e) => {
|
|
256
128
|
throw new WorkflowError({
|
|
@@ -260,55 +132,109 @@ export const sendEmail = async ({
|
|
|
260
132
|
});
|
|
261
133
|
const multiaddr = `/ipfs/${cid}`;
|
|
262
134
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
135
|
+
// Prepare secrets for the requester
|
|
136
|
+
// Use a positive integer as secret ID (required by iexec)
|
|
137
|
+
// Using "1" as a fixed ID for the requester secret
|
|
138
|
+
const requesterSecretId = 1;
|
|
139
|
+
const secrets = {
|
|
140
|
+
[requesterSecretId]: JSON.stringify({
|
|
141
|
+
senderName: vSenderName,
|
|
266
142
|
emailSubject: vEmailSubject,
|
|
267
143
|
emailContentMultiAddr: multiaddr,
|
|
268
144
|
contentType: vContentType,
|
|
269
|
-
senderName: vSenderName,
|
|
270
145
|
emailContentEncryptionKey,
|
|
271
|
-
|
|
272
|
-
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
// Bulk processing
|
|
149
|
+
if (grantedAccess) {
|
|
150
|
+
const vMaxProtectedDataPerTask = positiveNumberSchema()
|
|
151
|
+
.label('maxProtectedDataPerTask')
|
|
152
|
+
.validateSync(maxProtectedDataPerTask);
|
|
153
|
+
|
|
154
|
+
const bulkRequest = await dataProtector.prepareBulkRequest({
|
|
155
|
+
app: vDappAddressOrENS,
|
|
156
|
+
appMaxPrice: vAppMaxPrice,
|
|
157
|
+
workerpoolMaxPrice: vWorkerpoolMaxPrice,
|
|
158
|
+
workerpool: vWorkerpoolAddressOrEns,
|
|
159
|
+
args: vLabel,
|
|
160
|
+
inputFiles: [],
|
|
161
|
+
secrets,
|
|
162
|
+
bulkOrders: grantedAccess,
|
|
163
|
+
maxProtectedDataPerTask: vMaxProtectedDataPerTask,
|
|
164
|
+
});
|
|
165
|
+
const processBulkRequestResponse: ProcessBulkRequestResponse =
|
|
166
|
+
await dataProtector.processBulkRequest({
|
|
167
|
+
bulkRequest: bulkRequest.bulkRequest,
|
|
168
|
+
useVoucher: vUseVoucher,
|
|
169
|
+
workerpool: vWorkerpoolAddressOrEns,
|
|
170
|
+
});
|
|
171
|
+
return processBulkRequestResponse;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Single processing mode - protectedData is required
|
|
175
|
+
const vDatasetAddress = addressOrEnsSchema()
|
|
176
|
+
.required()
|
|
177
|
+
.label('protectedData')
|
|
178
|
+
.validateSync(protectedData);
|
|
179
|
+
// Check protected data validity through subgraph
|
|
180
|
+
const isValidProtectedData = await checkProtectedDataValidity(
|
|
181
|
+
graphQLClient,
|
|
182
|
+
vDatasetAddress
|
|
273
183
|
);
|
|
184
|
+
if (!isValidProtectedData) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
'This protected data does not contain "email:string" in its schema.'
|
|
187
|
+
);
|
|
188
|
+
}
|
|
274
189
|
|
|
275
|
-
|
|
190
|
+
// Use processProtectedData from dataprotector
|
|
191
|
+
const result = await dataProtector.processProtectedData({
|
|
192
|
+
defaultWorkerpool: vWorkerpoolAddressOrEns,
|
|
193
|
+
protectedData: vDatasetAddress,
|
|
276
194
|
app: vDappAddressOrENS,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
workerpoolmaxprice: workerpoolorder.workerpoolprice,
|
|
282
|
-
tag: ['tee', 'scone'],
|
|
195
|
+
// userWhitelist: vDappWhitelistAddress, // Removed due to bug in dataprotector v2.0.0-beta.20
|
|
196
|
+
dataMaxPrice: vDataMaxPrice,
|
|
197
|
+
appMaxPrice: vAppMaxPrice,
|
|
198
|
+
workerpoolMaxPrice: vWorkerpoolMaxPrice,
|
|
283
199
|
workerpool: vWorkerpoolAddressOrEns,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
iexec_args: vLabel,
|
|
290
|
-
},
|
|
200
|
+
args: vLabel,
|
|
201
|
+
inputFiles: [],
|
|
202
|
+
secrets,
|
|
203
|
+
useVoucher: vUseVoucher,
|
|
204
|
+
waitForResult: false,
|
|
291
205
|
});
|
|
292
|
-
const requestorder = await iexec.order.signRequestorder(requestorderToSign);
|
|
293
|
-
|
|
294
|
-
// Match orders and compute task ID
|
|
295
|
-
const { dealid } = await iexec.order.matchOrders(
|
|
296
|
-
{
|
|
297
|
-
apporder: apporder,
|
|
298
|
-
datasetorder: datasetorder,
|
|
299
|
-
workerpoolorder: workerpoolorder,
|
|
300
|
-
requestorder: requestorder,
|
|
301
|
-
},
|
|
302
|
-
{ useVoucher: vUseVoucher }
|
|
303
|
-
);
|
|
304
|
-
const taskId = await iexec.deal.computeTaskId(dealid, 0);
|
|
305
206
|
|
|
306
207
|
return {
|
|
307
|
-
taskId,
|
|
208
|
+
taskId: result.taskId,
|
|
308
209
|
};
|
|
309
210
|
} catch (error) {
|
|
211
|
+
// Protocol error detected, re-throwing as-is
|
|
212
|
+
if ((error as any)?.isProtocolError === true) {
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle protocol errors - this will throw if it's an ApiCallError
|
|
217
|
+
// handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true
|
|
310
218
|
handleIfProtocolError(error);
|
|
311
219
|
|
|
220
|
+
// If we reach here, it's not a protocol error
|
|
221
|
+
// Check if it's a WorkflowError from processProtectedData by checking the message
|
|
222
|
+
const isProcessProtectedDataError =
|
|
223
|
+
error instanceof Error &&
|
|
224
|
+
error.message === 'Failed to process protected data';
|
|
225
|
+
|
|
226
|
+
if (isProcessProtectedDataError) {
|
|
227
|
+
const cause = (error as any)?.cause;
|
|
228
|
+
// Return unwrapped cause (the actual Error object)
|
|
229
|
+
// error.cause should be an Error, but ensure it is
|
|
230
|
+
const unwrappedCause = cause instanceof Error ? cause : error;
|
|
231
|
+
throw new WorkflowError({
|
|
232
|
+
message: 'Failed to sendEmail',
|
|
233
|
+
errorCause: unwrappedCause,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// For all other errors
|
|
312
238
|
throw new WorkflowError({
|
|
313
239
|
message: 'Failed to sendEmail',
|
|
314
240
|
errorCause: error,
|
package/src/web3mail/types.ts
CHANGED
|
@@ -11,20 +11,41 @@ export type Address = string;
|
|
|
11
11
|
|
|
12
12
|
export type TimeStamp = string;
|
|
13
13
|
|
|
14
|
+
export type GrantedAccess = {
|
|
15
|
+
dataset: string;
|
|
16
|
+
datasetprice: string;
|
|
17
|
+
volume: string;
|
|
18
|
+
tag: string;
|
|
19
|
+
apprestrict: string;
|
|
20
|
+
workerpoolrestrict: string;
|
|
21
|
+
requesterrestrict: string;
|
|
22
|
+
salt: string;
|
|
23
|
+
sign: string;
|
|
24
|
+
remainingAccess: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
14
27
|
export type Contact = {
|
|
15
28
|
address: Address;
|
|
16
29
|
owner: Address;
|
|
17
30
|
accessGrantTimestamp: TimeStamp;
|
|
18
31
|
isUserStrict: boolean;
|
|
19
|
-
name
|
|
32
|
+
name?: string;
|
|
20
33
|
remainingAccess: number;
|
|
21
34
|
accessPrice: number;
|
|
35
|
+
grantedAccess: GrantedAccess;
|
|
22
36
|
};
|
|
23
37
|
|
|
24
38
|
export type SendEmailParams = {
|
|
25
39
|
emailSubject: string;
|
|
26
40
|
emailContent: string;
|
|
27
|
-
protectedData
|
|
41
|
+
protectedData?: Address;
|
|
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;
|
|
28
49
|
contentType?: string;
|
|
29
50
|
senderName?: string;
|
|
30
51
|
label?: string;
|
|
@@ -40,6 +61,7 @@ export type FetchMyContactsParams = {
|
|
|
40
61
|
* Get contacts for this specific user only
|
|
41
62
|
*/
|
|
42
63
|
isUserStrict?: boolean;
|
|
64
|
+
bulkOnly?: boolean;
|
|
43
65
|
};
|
|
44
66
|
|
|
45
67
|
export type FetchUserContactsParams = {
|
|
@@ -49,7 +71,7 @@ export type FetchUserContactsParams = {
|
|
|
49
71
|
userAddress: Address;
|
|
50
72
|
} & FetchMyContactsParams;
|
|
51
73
|
|
|
52
|
-
export type
|
|
74
|
+
export type SendEmailSingleResponse = {
|
|
53
75
|
taskId: string;
|
|
54
76
|
};
|
|
55
77
|
|