@iexec/web3mail 1.6.0 → 1.7.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/dist/utils/subgraphQuery.js +5 -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 -1
- package/dist/web3mail/IExecWeb3mail.js +38 -0
- package/dist/web3mail/IExecWeb3mail.js.map +1 -1
- package/dist/web3mail/fetchMyContacts.d.ts +1 -1
- package/dist/web3mail/fetchMyContacts.js +3 -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/prepareEmailCampaign.d.ts +4 -0
- package/dist/web3mail/prepareEmailCampaign.js +106 -0
- package/dist/web3mail/prepareEmailCampaign.js.map +1 -0
- package/dist/web3mail/sendEmail.js +9 -5
- 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 +96 -1
- package/package.json +5 -6
- package/src/utils/subgraphQuery.ts +17 -14
- package/src/utils/validators.ts +68 -1
- package/src/web3mail/IExecWeb3mail.ts +53 -0
- package/src/web3mail/fetchMyContacts.ts +3 -0
- package/src/web3mail/fetchUserContacts.ts +28 -11
- package/src/web3mail/internalTypes.ts +5 -0
- package/src/web3mail/prepareEmailCampaign.ts +170 -0
- package/src/web3mail/sendEmail.ts +31 -5
- package/src/web3mail/sendEmailCampaign.ts +69 -0
- package/src/web3mail/types.ts +102 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SendEmailCampaignParams, SendEmailCampaignResponse } from './types.js';
|
|
2
|
+
import { DataProtectorConsumer } from './internalTypes.js';
|
|
3
|
+
export type SendEmailCampaign = typeof sendEmailCampaign;
|
|
4
|
+
export declare const sendEmailCampaign: ({ dataProtector, workerpoolAddressOrEns, campaignRequest, }: DataProtectorConsumer & SendEmailCampaignParams) => Promise<SendEmailCampaignResponse>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NULL_ADDRESS } from 'iexec/utils';
|
|
2
|
+
import { ValidationError } from 'yup';
|
|
3
|
+
import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
|
|
4
|
+
import { addressOrEnsSchema, campaignRequestSchema, throwIfMissing, } from '../utils/validators.js';
|
|
5
|
+
export const sendEmailCampaign = async ({ dataProtector = throwIfMissing(), workerpoolAddressOrEns = throwIfMissing(), campaignRequest, }) => {
|
|
6
|
+
const vCampaignRequest = campaignRequestSchema()
|
|
7
|
+
.required()
|
|
8
|
+
.label('campaignRequest')
|
|
9
|
+
.validateSync(campaignRequest);
|
|
10
|
+
const vWorkerpoolAddressOrEns = addressOrEnsSchema()
|
|
11
|
+
.required()
|
|
12
|
+
.label('workerpoolAddressOrEns')
|
|
13
|
+
.validateSync(workerpoolAddressOrEns);
|
|
14
|
+
if (vCampaignRequest.workerpool !== NULL_ADDRESS &&
|
|
15
|
+
vCampaignRequest.workerpool.toLowerCase() !==
|
|
16
|
+
vWorkerpoolAddressOrEns.toLowerCase()) {
|
|
17
|
+
throw new ValidationError("workerpoolAddressOrEns doesn't match campaignRequest workerpool");
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
// Process the prepared bulk request
|
|
21
|
+
const processBulkRequestResponse = await dataProtector.processBulkRequest({
|
|
22
|
+
bulkRequest: vCampaignRequest,
|
|
23
|
+
workerpool: vWorkerpoolAddressOrEns,
|
|
24
|
+
waitForResult: false,
|
|
25
|
+
});
|
|
26
|
+
return processBulkRequestResponse;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// Protocol error detected, re-throwing as-is
|
|
30
|
+
if (error?.isProtocolError === true) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
// Handle protocol errors - this will throw if it's an ApiCallError
|
|
34
|
+
// handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true
|
|
35
|
+
handleIfProtocolError(error);
|
|
36
|
+
// For all other errors
|
|
37
|
+
throw new WorkflowError({
|
|
38
|
+
message: 'Failed to sendEmailCampaign',
|
|
39
|
+
errorCause: error,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=sendEmailCampaign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendEmailCampaign.js","sourceRoot":"","sources":["../../src/web3mail/sendEmailCampaign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,GACf,MAAM,wBAAwB,CAAC;AAUhC,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAAE,EACtC,aAAa,GAAG,cAAc,EAAE,EAChC,sBAAsB,GAAG,cAAc,EAAE,EACzC,eAAe,GAEQ,EAAsC,EAAE;IAC/D,MAAM,gBAAgB,GAAG,qBAAqB,EAAE;SAC7C,QAAQ,EAAE;SACV,KAAK,CAAC,iBAAiB,CAAC;SACxB,YAAY,CAAC,eAAe,CAAoB,CAAC;IAEpD,MAAM,uBAAuB,GAAG,kBAAkB,EAAE;SACjD,QAAQ,EAAE;SACV,KAAK,CAAC,wBAAwB,CAAC;SAC/B,YAAY,CAAC,sBAAsB,CAAC,CAAC;IAExC,IACE,gBAAgB,CAAC,UAAU,KAAK,YAAY;QAC5C,gBAAgB,CAAC,UAAU,CAAC,WAAW,EAAE;YACvC,uBAAuB,CAAC,WAAW,EAAE,EACvC,CAAC;QACD,MAAM,IAAI,eAAe,CACvB,iEAAiE,CAClE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,oCAAoC;QACpC,MAAM,0BAA0B,GAAG,MAAM,aAAa,CAAC,kBAAkB,CAAC;YACxE,WAAW,EAAE,gBAAgB;YAC7B,UAAU,EAAE,uBAAuB;YACnC,aAAa,EAAE,KAAK;SACrB,CAAC,CAAC;QAEH,OAAO,0BAA0B,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,6CAA6C;QAC7C,IAAK,KAAa,EAAE,eAAe,KAAK,IAAI,EAAE,CAAC;YAC7C,MAAM,KAAK,CAAC;QACd,CAAC;QAED,mEAAmE;QACnE,+FAA+F;QAC/F,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAE7B,uBAAuB;QACvB,MAAM,IAAI,aAAa,CAAC;YACtB,OAAO,EAAE,6BAA6B;YACtC,UAAU,EAAE,KAAK;SAClB,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC"}
|
package/dist/web3mail/types.d.ts
CHANGED
|
@@ -1,18 +1,47 @@
|
|
|
1
1
|
import { EnhancedWallet } from 'iexec';
|
|
2
2
|
import { IExecConfigOptions } from 'iexec/IExecConfig';
|
|
3
|
+
import type { BulkRequest } from '@iexec/dataprotector';
|
|
3
4
|
export type Web3SignerProvider = EnhancedWallet;
|
|
4
5
|
export type ENS = string;
|
|
5
6
|
export type AddressOrENS = Address | ENS;
|
|
6
7
|
export type Address = string;
|
|
7
8
|
export type TimeStamp = string;
|
|
9
|
+
/**
|
|
10
|
+
* request to send email in bulk
|
|
11
|
+
*
|
|
12
|
+
* use `prepareEmailCampaign()` to create a `CampaignRequest`
|
|
13
|
+
*
|
|
14
|
+
* then use `sendEmailCampaign()` to send the campaign
|
|
15
|
+
*/
|
|
16
|
+
export type CampaignRequest = BulkRequest;
|
|
17
|
+
/**
|
|
18
|
+
* authorization signed by the data owner granting access to this contact
|
|
19
|
+
*
|
|
20
|
+
* `GrantedAccess` are obtained by fetching contacts (e.g. `fetchMyContacts()` or `fetchUserContacts()`)
|
|
21
|
+
*
|
|
22
|
+
* `GrantedAccess` can be consumed for email campaigns (e.g. `prepareEmailCampaign()` then `sendEmailCampaign()`)
|
|
23
|
+
*/
|
|
24
|
+
export type GrantedAccess = {
|
|
25
|
+
dataset: string;
|
|
26
|
+
datasetprice: string;
|
|
27
|
+
volume: string;
|
|
28
|
+
tag: string;
|
|
29
|
+
apprestrict: string;
|
|
30
|
+
workerpoolrestrict: string;
|
|
31
|
+
requesterrestrict: string;
|
|
32
|
+
salt: string;
|
|
33
|
+
sign: string;
|
|
34
|
+
remainingAccess: number;
|
|
35
|
+
};
|
|
8
36
|
export type Contact = {
|
|
9
37
|
address: Address;
|
|
10
38
|
owner: Address;
|
|
11
39
|
accessGrantTimestamp: TimeStamp;
|
|
12
40
|
isUserStrict: boolean;
|
|
13
|
-
name
|
|
41
|
+
name?: string;
|
|
14
42
|
remainingAccess: number;
|
|
15
43
|
accessPrice: number;
|
|
44
|
+
grantedAccess: GrantedAccess;
|
|
16
45
|
};
|
|
17
46
|
export type SendEmailParams = {
|
|
18
47
|
emailSubject: string;
|
|
@@ -32,6 +61,10 @@ export type FetchMyContactsParams = {
|
|
|
32
61
|
* Get contacts for this specific user only
|
|
33
62
|
*/
|
|
34
63
|
isUserStrict?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* If true, returns only contacts with bulk processing access grants
|
|
66
|
+
*/
|
|
67
|
+
bulkOnly?: boolean;
|
|
35
68
|
};
|
|
36
69
|
export type FetchUserContactsParams = {
|
|
37
70
|
/**
|
|
@@ -40,7 +73,14 @@ export type FetchUserContactsParams = {
|
|
|
40
73
|
userAddress: Address;
|
|
41
74
|
} & FetchMyContactsParams;
|
|
42
75
|
export type SendEmailResponse = {
|
|
76
|
+
/**
|
|
77
|
+
* ID of the task
|
|
78
|
+
*/
|
|
43
79
|
taskId: string;
|
|
80
|
+
/**
|
|
81
|
+
* ID of the deal containing the task
|
|
82
|
+
*/
|
|
83
|
+
dealId: string;
|
|
44
84
|
};
|
|
45
85
|
/**
|
|
46
86
|
* Configuration options for Web3Mail.
|
|
@@ -83,3 +123,58 @@ export type Web3MailConfigOptions = {
|
|
|
83
123
|
*/
|
|
84
124
|
allowExperimentalNetworks?: boolean;
|
|
85
125
|
};
|
|
126
|
+
export type PrepareEmailCampaignParams = {
|
|
127
|
+
/**
|
|
128
|
+
* List of `GrantedAccess` to contacts to send emails to in bulk.
|
|
129
|
+
*
|
|
130
|
+
* use `fetchMyContacts({ bulkOnly: true })` to get granted accesses.
|
|
131
|
+
*/
|
|
132
|
+
grantedAccesses: GrantedAccess[];
|
|
133
|
+
maxProtectedDataPerTask?: number;
|
|
134
|
+
senderName?: string;
|
|
135
|
+
emailSubject: string;
|
|
136
|
+
emailContent: string;
|
|
137
|
+
contentType?: string;
|
|
138
|
+
label?: string;
|
|
139
|
+
workerpoolAddressOrEns?: AddressOrENS;
|
|
140
|
+
dataMaxPrice?: number;
|
|
141
|
+
appMaxPrice?: number;
|
|
142
|
+
workerpoolMaxPrice?: number;
|
|
143
|
+
};
|
|
144
|
+
export type PrepareEmailCampaignResponse = {
|
|
145
|
+
/**
|
|
146
|
+
* The prepared campaign request
|
|
147
|
+
*
|
|
148
|
+
* Use this in `sendEmailCampaign()` to start or continue sending the campaign
|
|
149
|
+
*/
|
|
150
|
+
campaignRequest: CampaignRequest;
|
|
151
|
+
};
|
|
152
|
+
export type SendEmailCampaignParams = {
|
|
153
|
+
/**
|
|
154
|
+
* The prepared campaign request from `prepareEmailCampaign()`
|
|
155
|
+
*/
|
|
156
|
+
campaignRequest: CampaignRequest;
|
|
157
|
+
/**
|
|
158
|
+
* Workerpool address or ENS to use for processing
|
|
159
|
+
*/
|
|
160
|
+
workerpoolAddressOrEns?: AddressOrENS;
|
|
161
|
+
};
|
|
162
|
+
export type SendEmailCampaignResponse = {
|
|
163
|
+
/**
|
|
164
|
+
* List of tasks created for the campaign
|
|
165
|
+
*/
|
|
166
|
+
tasks: Array<{
|
|
167
|
+
/**
|
|
168
|
+
* ID of the task
|
|
169
|
+
*/
|
|
170
|
+
taskId: string;
|
|
171
|
+
/**
|
|
172
|
+
* ID of the deal containing the task
|
|
173
|
+
*/
|
|
174
|
+
dealId: string;
|
|
175
|
+
/**
|
|
176
|
+
* Index of the task in the bulk request
|
|
177
|
+
*/
|
|
178
|
+
bulkIndex: number;
|
|
179
|
+
}>;
|
|
180
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iexec/web3mail",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "This product enables users to confidentially store data–such as mail address, documents, personal information ...",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "rm -rf dist && tsc --project tsconfig.build.json",
|
|
22
22
|
"check-types": "tsc --noEmit",
|
|
23
|
-
"test:prepare": "node tests/scripts/prepare-bellecour-fork-for-tests.js",
|
|
24
|
-
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/**/*.test.ts\" --forceExit -b",
|
|
23
|
+
"test:prepare": "node tests/scripts/prepare-bellecour-fork-for-tests.js && node tests/scripts/prepare-iexec.js",
|
|
25
24
|
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/**/*.test.ts\" --forceExit --coverage",
|
|
26
25
|
"test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/unit/**/*.test.ts\" -b",
|
|
27
26
|
"test:unit:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/unit/**/*.unit.ts\" --coverage",
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
"format": "prettier --write \"(src|tests)/**/*.ts\"",
|
|
32
31
|
"check-format": "prettier --check \"(src|tests)/**/*.ts\"",
|
|
33
32
|
"stop-test-stack": "cd tests && docker compose down --volumes --remove-orphans",
|
|
34
|
-
"start-test-stack": "cd tests && npm run stop-test-stack && node scripts/prepare-test-env.js && docker compose build && docker compose up -d &&
|
|
33
|
+
"start-test-stack": "cd tests && npm run stop-test-stack && node scripts/prepare-test-env.js && docker compose build && docker compose up -d && npm run test:prepare"
|
|
35
34
|
},
|
|
36
35
|
"repository": {
|
|
37
36
|
"type": "git",
|
|
@@ -49,15 +48,15 @@
|
|
|
49
48
|
"dependencies": {
|
|
50
49
|
"@ethersproject/bytes": "^5.7.0",
|
|
51
50
|
"@ethersproject/random": "^5.7.0",
|
|
51
|
+
"@iexec/dataprotector": "^2.0.0-beta.21",
|
|
52
52
|
"buffer": "^6.0.3",
|
|
53
53
|
"ethers": "^6.13.2",
|
|
54
54
|
"graphql-request": "^6.1.0",
|
|
55
|
-
"iexec": "^8.
|
|
55
|
+
"iexec": "^8.22.2",
|
|
56
56
|
"kubo-rpc-client": "^4.1.1",
|
|
57
57
|
"yup": "^1.1.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@iexec/dataprotector": "^2.0.0-beta.19",
|
|
61
60
|
"@jest/globals": "^29.7.0",
|
|
62
61
|
"@swc/core": "^1.3.96",
|
|
63
62
|
"@swc/jest": "^0.2.29",
|
|
@@ -71,20 +71,23 @@ export const getValidContact = async (
|
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
// Convert protectedData[] into Contact[] using the map for constant time lookups
|
|
74
|
-
return protectedDataList
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
return protectedDataList
|
|
75
|
+
.map(({ id, name }) => {
|
|
76
|
+
const contact = contactsMap.get(id);
|
|
77
|
+
if (contact) {
|
|
78
|
+
return {
|
|
79
|
+
address: id,
|
|
80
|
+
name: name,
|
|
81
|
+
remainingAccess: contact.remainingAccess,
|
|
82
|
+
accessPrice: contact.accessPrice,
|
|
83
|
+
owner: contact.owner,
|
|
84
|
+
accessGrantTimestamp: contact.accessGrantTimestamp,
|
|
85
|
+
isUserStrict: contact.isUserStrict,
|
|
86
|
+
grantedAccess: contact.grantedAccess,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.filter((contact) => !!contact);
|
|
88
91
|
} catch (error) {
|
|
89
92
|
throw new WorkflowError({
|
|
90
93
|
message: 'Failed to fetch subgraph',
|
package/src/utils/validators.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isAddress } from 'ethers';
|
|
2
2
|
import { IExec } from 'iexec';
|
|
3
|
-
import {
|
|
3
|
+
import { NULL_ADDRESS } from 'iexec/utils';
|
|
4
|
+
import { ValidationError, boolean, number, object, string } from 'yup';
|
|
4
5
|
|
|
5
6
|
export const isValidProvider = async (iexec: IExec) => {
|
|
6
7
|
const client = await iexec.config.resolveContractsClient();
|
|
@@ -61,3 +62,69 @@ export const positiveNumberSchema = () =>
|
|
|
61
62
|
|
|
62
63
|
export const booleanSchema = () =>
|
|
63
64
|
boolean().strict().typeError('${path} should be a boolean');
|
|
65
|
+
|
|
66
|
+
const isPositiveIntegerStringTest = (value: string) => /^\d+$/.test(value);
|
|
67
|
+
|
|
68
|
+
const stringSchema = () =>
|
|
69
|
+
string().strict().typeError('${path} should be a string');
|
|
70
|
+
|
|
71
|
+
const positiveIntegerStringSchema = () =>
|
|
72
|
+
string().test(
|
|
73
|
+
'is-positive-int',
|
|
74
|
+
'${path} should be a positive integer',
|
|
75
|
+
(value) => isUndefined(value) || isPositiveIntegerStringTest(value)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const positiveStrictIntegerStringSchema = () =>
|
|
79
|
+
string().test(
|
|
80
|
+
'is-positive-strict-int',
|
|
81
|
+
'${path} should be a strictly positive integer',
|
|
82
|
+
(value) =>
|
|
83
|
+
isUndefined(value) ||
|
|
84
|
+
(value !== '0' && isPositiveIntegerStringTest(value))
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export const campaignRequestSchema = () =>
|
|
88
|
+
object({
|
|
89
|
+
app: addressSchema().required(),
|
|
90
|
+
appmaxprice: positiveIntegerStringSchema().required(),
|
|
91
|
+
workerpool: addressSchema().required(),
|
|
92
|
+
workerpoolmaxprice: positiveIntegerStringSchema().required(),
|
|
93
|
+
dataset: addressSchema().oneOf([NULL_ADDRESS]).required(),
|
|
94
|
+
datasetmaxprice: positiveIntegerStringSchema().oneOf(['0']).required(),
|
|
95
|
+
params: stringSchema()
|
|
96
|
+
.test(
|
|
97
|
+
'is-valid-bulk-params',
|
|
98
|
+
'${path} should be a valid JSON string with bulk_cid field',
|
|
99
|
+
(value) => {
|
|
100
|
+
try {
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
102
|
+
const { bulk_cid } = JSON.parse(value);
|
|
103
|
+
if (typeof bulk_cid === 'string') {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
} catch {}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
.required(),
|
|
111
|
+
requester: addressSchema().required(),
|
|
112
|
+
beneficiary: addressSchema().required(),
|
|
113
|
+
callback: addressSchema().required(),
|
|
114
|
+
category: positiveIntegerStringSchema().required(),
|
|
115
|
+
volume: positiveStrictIntegerStringSchema().required(),
|
|
116
|
+
tag: stringSchema().required(),
|
|
117
|
+
trust: positiveIntegerStringSchema().required(),
|
|
118
|
+
salt: stringSchema().required(),
|
|
119
|
+
sign: stringSchema().required(),
|
|
120
|
+
})
|
|
121
|
+
.strict()
|
|
122
|
+
.typeError('${path} should be a BulkRequest object')
|
|
123
|
+
.test('is-defined', '${path} is required', (value) => {
|
|
124
|
+
// Check if value is undefined, null, or an empty object (which would be coerced from undefined)
|
|
125
|
+
return (
|
|
126
|
+
value !== undefined &&
|
|
127
|
+
value !== null &&
|
|
128
|
+
!(typeof value === 'object' && Object.keys(value).length === 0)
|
|
129
|
+
);
|
|
130
|
+
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { AbstractProvider, AbstractSigner, Eip1193Provider } from 'ethers';
|
|
2
2
|
import { IExec } from 'iexec';
|
|
3
|
+
import { IExecDataProtectorCore } from '@iexec/dataprotector';
|
|
3
4
|
import { GraphQLClient } from 'graphql-request';
|
|
4
5
|
import { fetchUserContacts } from './fetchUserContacts.js';
|
|
5
6
|
import { fetchMyContacts } from './fetchMyContacts.js';
|
|
6
7
|
import { sendEmail } from './sendEmail.js';
|
|
8
|
+
import { prepareEmailCampaign } from './prepareEmailCampaign.js';
|
|
9
|
+
import { sendEmailCampaign } from './sendEmailCampaign.js';
|
|
7
10
|
import {
|
|
8
11
|
Contact,
|
|
9
12
|
FetchUserContactsParams,
|
|
@@ -13,6 +16,10 @@ import {
|
|
|
13
16
|
SendEmailResponse,
|
|
14
17
|
Web3SignerProvider,
|
|
15
18
|
FetchMyContactsParams,
|
|
19
|
+
PrepareEmailCampaignParams,
|
|
20
|
+
PrepareEmailCampaignResponse,
|
|
21
|
+
SendEmailCampaignParams,
|
|
22
|
+
SendEmailCampaignResponse,
|
|
16
23
|
} from './types.js';
|
|
17
24
|
import { isValidProvider } from '../utils/validators.js';
|
|
18
25
|
import { getChainIdFromProvider } from '../utils/getChainId.js';
|
|
@@ -34,6 +41,7 @@ interface Web3mailResolvedConfig {
|
|
|
34
41
|
ipfsGateway: string;
|
|
35
42
|
defaultWorkerpool: string;
|
|
36
43
|
iexec: IExec;
|
|
44
|
+
dataProtector: IExecDataProtectorCore;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
export class IExecWeb3mail {
|
|
@@ -51,6 +59,8 @@ export class IExecWeb3mail {
|
|
|
51
59
|
|
|
52
60
|
private iexec!: IExec;
|
|
53
61
|
|
|
62
|
+
private dataProtector!: IExecDataProtectorCore;
|
|
63
|
+
|
|
54
64
|
private initPromise: Promise<void> | null = null;
|
|
55
65
|
|
|
56
66
|
private ethProvider: EthersCompatibleProvider;
|
|
@@ -75,6 +85,7 @@ export class IExecWeb3mail {
|
|
|
75
85
|
this.ipfsGateway = config.ipfsGateway;
|
|
76
86
|
this.defaultWorkerpool = config.defaultWorkerpool;
|
|
77
87
|
this.iexec = config.iexec;
|
|
88
|
+
this.dataProtector = config.dataProtector;
|
|
78
89
|
});
|
|
79
90
|
}
|
|
80
91
|
return this.initPromise;
|
|
@@ -121,6 +132,36 @@ export class IExecWeb3mail {
|
|
|
121
132
|
});
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
async prepareEmailCampaign(
|
|
136
|
+
args: PrepareEmailCampaignParams
|
|
137
|
+
): Promise<PrepareEmailCampaignResponse> {
|
|
138
|
+
await this.init();
|
|
139
|
+
await isValidProvider(this.iexec);
|
|
140
|
+
return prepareEmailCampaign({
|
|
141
|
+
...args,
|
|
142
|
+
workerpoolAddressOrEns:
|
|
143
|
+
args.workerpoolAddressOrEns || this.defaultWorkerpool,
|
|
144
|
+
iexec: this.iexec,
|
|
145
|
+
dataProtector: this.dataProtector,
|
|
146
|
+
ipfsNode: this.ipfsNode,
|
|
147
|
+
ipfsGateway: this.ipfsGateway,
|
|
148
|
+
dappAddressOrENS: this.dappAddressOrENS,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async sendEmailCampaign(
|
|
153
|
+
args: SendEmailCampaignParams
|
|
154
|
+
): Promise<SendEmailCampaignResponse> {
|
|
155
|
+
await this.init();
|
|
156
|
+
await isValidProvider(this.iexec);
|
|
157
|
+
return sendEmailCampaign({
|
|
158
|
+
...args,
|
|
159
|
+
workerpoolAddressOrEns:
|
|
160
|
+
args.workerpoolAddressOrEns || this.defaultWorkerpool,
|
|
161
|
+
dataProtector: this.dataProtector,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
124
165
|
private async resolveConfig(): Promise<Web3mailResolvedConfig> {
|
|
125
166
|
const chainId = await getChainIdFromProvider(this.ethProvider);
|
|
126
167
|
const chainDefaultConfig = getChainDefaultConfig(chainId, {
|
|
@@ -184,6 +225,17 @@ export class IExecWeb3mail {
|
|
|
184
225
|
throw new Error(`Failed to create GraphQLClient: ${error.message}`);
|
|
185
226
|
}
|
|
186
227
|
|
|
228
|
+
const dataProtector = new IExecDataProtectorCore(this.ethProvider, {
|
|
229
|
+
iexecOptions: {
|
|
230
|
+
ipfsGatewayURL: ipfsGateway,
|
|
231
|
+
...this.options?.iexecOptions,
|
|
232
|
+
allowExperimentalNetworks: this.options.allowExperimentalNetworks,
|
|
233
|
+
},
|
|
234
|
+
ipfsGateway,
|
|
235
|
+
ipfsNode,
|
|
236
|
+
subgraphUrl,
|
|
237
|
+
});
|
|
238
|
+
|
|
187
239
|
return {
|
|
188
240
|
dappAddressOrENS,
|
|
189
241
|
dappWhitelistAddress: dappWhitelistAddress.toLowerCase(),
|
|
@@ -192,6 +244,7 @@ export class IExecWeb3mail {
|
|
|
192
244
|
ipfsNode,
|
|
193
245
|
ipfsGateway,
|
|
194
246
|
iexec,
|
|
247
|
+
dataProtector,
|
|
195
248
|
};
|
|
196
249
|
}
|
|
197
250
|
}
|
|
@@ -16,6 +16,7 @@ export const fetchMyContacts = async ({
|
|
|
16
16
|
dappAddressOrENS = throwIfMissing(),
|
|
17
17
|
dappWhitelistAddress = throwIfMissing(),
|
|
18
18
|
isUserStrict = false,
|
|
19
|
+
bulkOnly = false,
|
|
19
20
|
}: IExecConsumer &
|
|
20
21
|
SubgraphConsumer &
|
|
21
22
|
DappAddressConsumer &
|
|
@@ -24,6 +25,7 @@ export const fetchMyContacts = async ({
|
|
|
24
25
|
const vIsUserStrict = booleanSchema()
|
|
25
26
|
.label('isUserStrict')
|
|
26
27
|
.validateSync(isUserStrict);
|
|
28
|
+
const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly);
|
|
27
29
|
|
|
28
30
|
const userAddress = await iexec.wallet.getAddress();
|
|
29
31
|
return fetchUserContacts({
|
|
@@ -33,5 +35,6 @@ export const fetchMyContacts = async ({
|
|
|
33
35
|
dappWhitelistAddress,
|
|
34
36
|
userAddress,
|
|
35
37
|
isUserStrict: vIsUserStrict,
|
|
38
|
+
bulkOnly: vBulkOnly,
|
|
36
39
|
});
|
|
37
40
|
};
|
|
@@ -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,12 +57,14 @@ 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
70
|
|
|
@@ -84,6 +88,18 @@ export const fetchUserContacts = async ({
|
|
|
84
88
|
accessPrice: order.order.datasetprice,
|
|
85
89
|
accessGrantTimestamp: order.publicationTimestamp,
|
|
86
90
|
isUserStrict: order.order.requesterrestrict !== ZeroAddress,
|
|
91
|
+
grantedAccess: {
|
|
92
|
+
dataset: order.order.dataset,
|
|
93
|
+
datasetprice: order.order.datasetprice.toString(),
|
|
94
|
+
volume: order.order.volume.toString(),
|
|
95
|
+
tag: order.order.tag.toString(),
|
|
96
|
+
apprestrict: order.order.apprestrict,
|
|
97
|
+
workerpoolrestrict: order.order.workerpoolrestrict,
|
|
98
|
+
requesterrestrict: order.order.requesterrestrict,
|
|
99
|
+
salt: order.order.salt,
|
|
100
|
+
sign: order.order.sign,
|
|
101
|
+
remainingAccess: order.remaining,
|
|
102
|
+
},
|
|
87
103
|
};
|
|
88
104
|
myContacts.push(contact);
|
|
89
105
|
}
|
|
@@ -107,23 +123,24 @@ async function fetchAllOrdersByApp({
|
|
|
107
123
|
userAddress,
|
|
108
124
|
appAddress,
|
|
109
125
|
isUserStrict,
|
|
126
|
+
bulkOnly,
|
|
110
127
|
}: {
|
|
111
128
|
iexec: IExec;
|
|
112
129
|
userAddress: string;
|
|
113
130
|
appAddress: string;
|
|
114
131
|
isUserStrict: boolean;
|
|
132
|
+
bulkOnly: boolean;
|
|
115
133
|
}): Promise<PublishedDatasetorder[]> {
|
|
116
|
-
const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook(
|
|
117
|
-
ANY_DATASET_ADDRESS,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
134
|
+
const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook({
|
|
135
|
+
dataset: ANY_DATASET_ADDRESS,
|
|
136
|
+
app: appAddress,
|
|
137
|
+
requester: userAddress,
|
|
138
|
+
isAppStrict: true,
|
|
139
|
+
isRequesterStrict: isUserStrict,
|
|
140
|
+
bulkOnly,
|
|
141
|
+
// Use maxPageSize here to avoid too many round-trips (we want everything anyway)
|
|
142
|
+
pageSize: 1000,
|
|
143
|
+
});
|
|
127
144
|
const { orders: allOrders } = await autoPaginateRequest({
|
|
128
145
|
request: ordersFirstPage,
|
|
129
146
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IExec } from 'iexec';
|
|
2
|
+
import { IExecDataProtectorCore } from '@iexec/dataprotector';
|
|
2
3
|
import { AddressOrENS } from './types.js';
|
|
3
4
|
import { GraphQLClient } from 'graphql-request';
|
|
4
5
|
|
|
@@ -34,3 +35,7 @@ export type IExecConsumer = {
|
|
|
34
35
|
export type SubgraphConsumer = {
|
|
35
36
|
graphQLClient: GraphQLClient;
|
|
36
37
|
};
|
|
38
|
+
|
|
39
|
+
export type DataProtectorConsumer = {
|
|
40
|
+
dataProtector: IExecDataProtectorCore;
|
|
41
|
+
};
|