@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.
Files changed (34) hide show
  1. package/dist/utils/subgraphQuery.js +1 -2
  2. package/dist/utils/subgraphQuery.js.map +1 -1
  3. package/dist/utils/validators.d.ts +35 -0
  4. package/dist/utils/validators.js +45 -1
  5. package/dist/utils/validators.js.map +1 -1
  6. package/dist/web3mail/IExecWeb3mail.d.ts +4 -2
  7. package/dist/web3mail/IExecWeb3mail.js +24 -1
  8. package/dist/web3mail/IExecWeb3mail.js.map +1 -1
  9. package/dist/web3mail/fetchMyContacts.js +2 -1
  10. package/dist/web3mail/fetchMyContacts.js.map +1 -1
  11. package/dist/web3mail/fetchUserContacts.js +1 -1
  12. package/dist/web3mail/fetchUserContacts.js.map +1 -1
  13. package/dist/web3mail/internalTypes.d.ts +4 -4
  14. package/dist/web3mail/prepareEmailCampaign.d.ts +4 -0
  15. package/dist/web3mail/prepareEmailCampaign.js +106 -0
  16. package/dist/web3mail/prepareEmailCampaign.js.map +1 -0
  17. package/dist/web3mail/sendEmail.d.ts +2 -2
  18. package/dist/web3mail/sendEmail.js +191 -135
  19. package/dist/web3mail/sendEmail.js.map +1 -1
  20. package/dist/web3mail/sendEmailCampaign.d.ts +4 -0
  21. package/dist/web3mail/sendEmailCampaign.js +43 -0
  22. package/dist/web3mail/sendEmailCampaign.js.map +1 -0
  23. package/dist/web3mail/types.d.ts +83 -24
  24. package/package.json +3 -4
  25. package/src/utils/subgraphQuery.ts +1 -2
  26. package/src/utils/validators.ts +68 -1
  27. package/src/web3mail/IExecWeb3mail.ts +38 -5
  28. package/src/web3mail/fetchMyContacts.ts +2 -1
  29. package/src/web3mail/fetchUserContacts.ts +7 -5
  30. package/src/web3mail/internalTypes.ts +5 -3
  31. package/src/web3mail/prepareEmailCampaign.ts +170 -0
  32. package/src/web3mail/sendEmail.ts +246 -150
  33. package/src/web3mail/sendEmailCampaign.ts +69 -0
  34. package/src/web3mail/types.ts +88 -26
@@ -1,6 +1,7 @@
1
1
  import { isAddress } from 'ethers';
2
2
  import { IExec } from 'iexec';
3
- import { ValidationError, boolean, number, string } from 'yup';
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
+ });
@@ -5,6 +5,8 @@ import { GraphQLClient } from 'graphql-request';
5
5
  import { fetchUserContacts } from './fetchUserContacts.js';
6
6
  import { fetchMyContacts } from './fetchMyContacts.js';
7
7
  import { sendEmail } from './sendEmail.js';
8
+ import { prepareEmailCampaign } from './prepareEmailCampaign.js';
9
+ import { sendEmailCampaign } from './sendEmailCampaign.js';
8
10
  import {
9
11
  Contact,
10
12
  FetchUserContactsParams,
@@ -14,6 +16,10 @@ import {
14
16
  SendEmailResponse,
15
17
  Web3SignerProvider,
16
18
  FetchMyContactsParams,
19
+ PrepareEmailCampaignParams,
20
+ PrepareEmailCampaignResponse,
21
+ SendEmailCampaignParams,
22
+ SendEmailCampaignResponse,
17
23
  } from './types.js';
18
24
  import { isValidProvider } from '../utils/validators.js';
19
25
  import { getChainIdFromProvider } from '../utils/getChainId.js';
@@ -110,9 +116,7 @@ export class IExecWeb3mail {
110
116
  });
111
117
  }
112
118
 
113
- async sendEmail<Params extends SendEmailParams>(
114
- args: Params
115
- ): Promise<SendEmailResponse<Params>> {
119
+ async sendEmail(args: SendEmailParams): Promise<SendEmailResponse> {
116
120
  await this.init();
117
121
  await isValidProvider(this.iexec);
118
122
  return sendEmail({
@@ -120,13 +124,42 @@ export class IExecWeb3mail {
120
124
  workerpoolAddressOrEns:
121
125
  args.workerpoolAddressOrEns || this.defaultWorkerpool,
122
126
  iexec: this.iexec,
123
- dataProtector: this.dataProtector,
124
127
  ipfsNode: this.ipfsNode,
125
128
  ipfsGateway: this.ipfsGateway,
126
129
  dappAddressOrENS: this.dappAddressOrENS,
127
130
  dappWhitelistAddress: this.dappWhitelistAddress,
128
131
  graphQLClient: this.graphQLClient,
129
- }) as Promise<SendEmailResponse<Params>>;
132
+ });
133
+ }
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
+ });
130
163
  }
131
164
 
132
165
  private async resolveConfig(): Promise<Web3mailResolvedConfig> {
@@ -25,6 +25,7 @@ export const fetchMyContacts = async ({
25
25
  const vIsUserStrict = booleanSchema()
26
26
  .label('isUserStrict')
27
27
  .validateSync(isUserStrict);
28
+ const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly);
28
29
 
29
30
  const userAddress = await iexec.wallet.getAddress();
30
31
  return fetchUserContacts({
@@ -34,6 +35,6 @@ export const fetchMyContacts = async ({
34
35
  dappWhitelistAddress,
35
36
  userAddress,
36
37
  isUserStrict: vIsUserStrict,
37
- bulkOnly,
38
+ bulkOnly: vBulkOnly,
38
39
  });
39
40
  };
@@ -12,7 +12,7 @@ import {
12
12
  isEnsTest,
13
13
  throwIfMissing,
14
14
  } from '../utils/validators.js';
15
- import { Contact, FetchUserContactsParams, GrantedAccess } from './types.js';
15
+ import { Contact, FetchUserContactsParams } from './types.js';
16
16
  import {
17
17
  DappAddressConsumer,
18
18
  DappWhitelistAddressConsumer,
@@ -67,6 +67,7 @@ export const fetchUserContacts = async ({
67
67
  bulkOnly: vBulkOnly,
68
68
  }),
69
69
  ]);
70
+
70
71
  const orders = dappOrders.concat(whitelistOrders);
71
72
  const myContacts: Omit<Contact, 'name'>[] = [];
72
73
  let web3DappResolvedAddress = vDappAddressOrENS;
@@ -80,7 +81,7 @@ export const fetchUserContacts = async ({
80
81
  order.order.apprestrict.toLowerCase() ===
81
82
  vDappWhitelistAddress.toLowerCase()
82
83
  ) {
83
- const contact: Contact = {
84
+ const contact = {
84
85
  address: order.order.dataset.toLowerCase(),
85
86
  owner: order.signer.toLowerCase(),
86
87
  remainingAccess: order.remaining,
@@ -91,14 +92,14 @@ export const fetchUserContacts = async ({
91
92
  dataset: order.order.dataset,
92
93
  datasetprice: order.order.datasetprice.toString(),
93
94
  volume: order.order.volume.toString(),
94
- tag: order.order.tag,
95
+ tag: order.order.tag.toString(),
95
96
  apprestrict: order.order.apprestrict,
96
97
  workerpoolrestrict: order.order.workerpoolrestrict,
97
98
  requesterrestrict: order.order.requesterrestrict,
98
99
  salt: order.order.salt,
99
100
  sign: order.order.sign,
100
101
  remainingAccess: order.remaining,
101
- } as GrantedAccess,
102
+ },
102
103
  };
103
104
  myContacts.push(contact);
104
105
  }
@@ -109,6 +110,7 @@ export const fetchUserContacts = async ({
109
110
  return await getValidContact(graphQLClient, myContacts);
110
111
  } catch (error) {
111
112
  handleIfProtocolError(error);
113
+
112
114
  throw new WorkflowError({
113
115
  message: 'Failed to fetch user contacts',
114
116
  errorCause: error,
@@ -127,7 +129,7 @@ async function fetchAllOrdersByApp({
127
129
  userAddress: string;
128
130
  appAddress: string;
129
131
  isUserStrict: boolean;
130
- bulkOnly?: boolean;
132
+ bulkOnly: boolean;
131
133
  }): Promise<PublishedDatasetorder[]> {
132
134
  const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook({
133
135
  dataset: ANY_DATASET_ADDRESS,
@@ -1,7 +1,7 @@
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
- import { IExecDataProtectorCore } from '@iexec/dataprotector';
5
5
 
6
6
  export type ProtectedDataQuery = {
7
7
  id: string;
@@ -32,8 +32,10 @@ export type IExecConsumer = {
32
32
  iexec: IExec;
33
33
  };
34
34
 
35
- export type DataProtectorConsumer = { dataProtector: IExecDataProtectorCore };
36
-
37
35
  export type SubgraphConsumer = {
38
36
  graphQLClient: GraphQLClient;
39
37
  };
38
+
39
+ export type DataProtectorConsumer = {
40
+ dataProtector: IExecDataProtectorCore;
41
+ };
@@ -0,0 +1,170 @@
1
+ import { Buffer } from 'buffer';
2
+ import {
3
+ DEFAULT_CONTENT_TYPE,
4
+ MAX_DESIRED_APP_ORDER_PRICE,
5
+ MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
6
+ } from '../config/config.js';
7
+ import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
8
+ import * as ipfs from '../utils/ipfs-service.js';
9
+ import {
10
+ addressOrEnsSchema,
11
+ contentTypeSchema,
12
+ emailContentSchema,
13
+ emailSubjectSchema,
14
+ labelSchema,
15
+ positiveNumberSchema,
16
+ senderNameSchema,
17
+ throwIfMissing,
18
+ } from '../utils/validators.js';
19
+ import {
20
+ PrepareEmailCampaignParams,
21
+ PrepareEmailCampaignResponse,
22
+ } from './types.js';
23
+ import {
24
+ DappAddressConsumer,
25
+ DataProtectorConsumer,
26
+ IExecConsumer,
27
+ IpfsGatewayConfigConsumer,
28
+ IpfsNodeConfigConsumer,
29
+ } from './internalTypes.js';
30
+
31
+ export type PrepareEmailCampaign = typeof prepareEmailCampaign;
32
+
33
+ export const prepareEmailCampaign = async ({
34
+ iexec = throwIfMissing(),
35
+ dataProtector = throwIfMissing(),
36
+ workerpoolAddressOrEns,
37
+ dappAddressOrENS,
38
+ ipfsNode,
39
+ ipfsGateway,
40
+ senderName,
41
+ emailSubject,
42
+ emailContent,
43
+ contentType = DEFAULT_CONTENT_TYPE,
44
+ label,
45
+ appMaxPrice = MAX_DESIRED_APP_ORDER_PRICE,
46
+ workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
47
+ grantedAccesses,
48
+ maxProtectedDataPerTask,
49
+ }: IExecConsumer &
50
+ DappAddressConsumer &
51
+ IpfsNodeConfigConsumer &
52
+ IpfsGatewayConfigConsumer &
53
+ DataProtectorConsumer &
54
+ PrepareEmailCampaignParams): Promise<PrepareEmailCampaignResponse> => {
55
+ try {
56
+ const vWorkerpoolAddressOrEns = addressOrEnsSchema()
57
+ .label('WorkerpoolAddressOrEns')
58
+ .validateSync(workerpoolAddressOrEns);
59
+
60
+ const vSenderName = senderNameSchema()
61
+ .label('senderName')
62
+ .validateSync(senderName);
63
+
64
+ const vEmailSubject = emailSubjectSchema()
65
+ .required()
66
+ .label('emailSubject')
67
+ .validateSync(emailSubject);
68
+
69
+ const vEmailContent = emailContentSchema()
70
+ .required()
71
+ .label('emailContent')
72
+ .validateSync(emailContent);
73
+
74
+ const vContentType = contentTypeSchema()
75
+ .label('contentType')
76
+ .validateSync(contentType);
77
+
78
+ const vLabel = labelSchema().label('label').validateSync(label);
79
+
80
+ const vDappAddressOrENS = addressOrEnsSchema()
81
+ .required()
82
+ .label('dappAddressOrENS')
83
+ .validateSync(dappAddressOrENS);
84
+
85
+ const vAppMaxPrice = positiveNumberSchema()
86
+ .label('appMaxPrice')
87
+ .validateSync(appMaxPrice);
88
+
89
+ const vWorkerpoolMaxPrice = positiveNumberSchema()
90
+ .label('workerpoolMaxPrice')
91
+ .validateSync(workerpoolMaxPrice);
92
+
93
+ const vMaxProtectedDataPerTask = positiveNumberSchema()
94
+ .label('maxProtectedDataPerTask')
95
+ .validateSync(maxProtectedDataPerTask);
96
+
97
+ // TODO: factor this
98
+ // Encrypt email content
99
+ const emailContentEncryptionKey = iexec.dataset.generateEncryptionKey();
100
+ const encryptedFile = await iexec.dataset
101
+ .encrypt(Buffer.from(vEmailContent, 'utf8'), emailContentEncryptionKey)
102
+ .catch((e) => {
103
+ throw new WorkflowError({
104
+ message: 'Failed to encrypt email content',
105
+ errorCause: e,
106
+ });
107
+ });
108
+
109
+ // Push email content to IPFS
110
+ const cid = await ipfs
111
+ .add(encryptedFile, {
112
+ ipfsNode,
113
+ ipfsGateway,
114
+ })
115
+ .catch((e) => {
116
+ throw new WorkflowError({
117
+ message: 'Failed to upload encrypted email content',
118
+ errorCause: e,
119
+ });
120
+ });
121
+
122
+ const multiaddr = `/ipfs/${cid}`;
123
+
124
+ // Prepare secrets for the requester
125
+ // Use a positive integer as secret ID (required by iexec)
126
+ // Using "1" as a fixed ID for the requester secret
127
+ const requesterSecretId = 1;
128
+ const secrets = {
129
+ [requesterSecretId]: JSON.stringify({
130
+ emailSubject: vEmailSubject,
131
+ emailContentMultiAddr: multiaddr,
132
+ contentType: vContentType,
133
+ senderName: vSenderName,
134
+ emailContentEncryptionKey,
135
+ useCallback: true,
136
+ }),
137
+ };
138
+
139
+ // TODO: end factor this
140
+ const { bulkRequest: campaignRequest } =
141
+ await dataProtector.prepareBulkRequest({
142
+ app: vDappAddressOrENS,
143
+ appMaxPrice: vAppMaxPrice,
144
+ workerpoolMaxPrice: vWorkerpoolMaxPrice,
145
+ workerpool: vWorkerpoolAddressOrEns,
146
+ args: vLabel,
147
+ inputFiles: [],
148
+ secrets,
149
+ bulkAccesses: grantedAccesses,
150
+ maxProtectedDataPerTask: vMaxProtectedDataPerTask,
151
+ });
152
+
153
+ return { campaignRequest };
154
+ } catch (error) {
155
+ // Protocol error detected, re-throwing as-is
156
+ if ((error as any)?.isProtocolError === true) {
157
+ throw error;
158
+ }
159
+
160
+ // Handle protocol errors - this will throw if it's an ApiCallError
161
+ // handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true
162
+ handleIfProtocolError(error);
163
+
164
+ // For all other errors
165
+ throw new WorkflowError({
166
+ message: 'Failed to prepareEmailCampaign',
167
+ errorCause: error,
168
+ });
169
+ }
170
+ };