@iexec/web3mail 0.2.0 → 0.4.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.
@@ -4,3 +4,5 @@ export declare const DATAPROTECTOR_SUBGRAPH_ENDPOINT = "https://thegraph-product
4
4
  export declare const MAX_DESIRED_APP_ORDER_PRICE = 0;
5
5
  export declare const MAX_DESIRED_WORKERPOOL_ORDER_PRICE = 0;
6
6
  export declare const DEFAULT_CONTENT_TYPE = "text/plain";
7
+ export declare const IPFS_UPLOAD_URL = "/dns4/ipfs-upload.v8-bellecour.iex.ec/https";
8
+ export declare const DEFAULT_IPFS_GATEWAY = "https://ipfs-gateway.v8-bellecour.iex.ec";
@@ -4,3 +4,5 @@ export const DATAPROTECTOR_SUBGRAPH_ENDPOINT = 'https://thegraph-product.iex.ec/
4
4
  export const MAX_DESIRED_APP_ORDER_PRICE = 0;
5
5
  export const MAX_DESIRED_WORKERPOOL_ORDER_PRICE = 0;
6
6
  export const DEFAULT_CONTENT_TYPE = 'text/plain';
7
+ export const IPFS_UPLOAD_URL = '/dns4/ipfs-upload.v8-bellecour.iex.ec/https';
8
+ export const DEFAULT_IPFS_GATEWAY = 'https://ipfs-gateway.v8-bellecour.iex.ec';
@@ -1 +1 @@
1
- export declare function generateSecureUniqueId(length: number): string;
1
+ export declare function generateSecureUniqueId(length: any): string;
@@ -1,5 +1,5 @@
1
- import crypto from 'crypto';
1
+ import { randomBytes } from '@ethersproject/random';
2
+ import { hexlify } from '@ethersproject/bytes';
2
3
  export function generateSecureUniqueId(length) {
3
- const buffer = crypto.randomBytes(length);
4
- return buffer.toString('hex');
4
+ return hexlify(randomBytes(length));
5
5
  }
@@ -0,0 +1,7 @@
1
+ declare const get: (cid: any, { ipfsGateway }?: {
2
+ ipfsGateway?: string;
3
+ }) => Promise<Uint8Array>;
4
+ declare const add: (content: any, { ipfsGateway }?: {
5
+ ipfsGateway?: string;
6
+ }) => Promise<any>;
7
+ export { add, get };
@@ -0,0 +1,28 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { create } from 'kubo-rpc-client';
11
+ import { DEFAULT_IPFS_GATEWAY, IPFS_UPLOAD_URL } from './../config/config.js';
12
+ const get = (cid, { ipfsGateway = DEFAULT_IPFS_GATEWAY } = {}) => __awaiter(void 0, void 0, void 0, function* () {
13
+ const multiaddr = `/ipfs/${cid.toString()}`;
14
+ const publicUrl = `${ipfsGateway}${multiaddr}`;
15
+ const res = yield fetch(publicUrl);
16
+ if (!res.ok) {
17
+ throw Error(`Failed to load content from ${publicUrl}`);
18
+ }
19
+ const arrayBuffer = yield res.arrayBuffer();
20
+ return new Uint8Array(arrayBuffer);
21
+ });
22
+ const add = (content, { ipfsGateway = DEFAULT_IPFS_GATEWAY } = {}) => __awaiter(void 0, void 0, void 0, function* () {
23
+ const ipfsClient = create(IPFS_UPLOAD_URL);
24
+ const { cid } = yield ipfsClient.add(content);
25
+ yield get(cid.toString(), { ipfsGateway });
26
+ return cid.toString();
27
+ });
28
+ export { add, get };
@@ -3,3 +3,4 @@ export declare const addressOrEnsSchema: () => import("yup").StringSchema<string
3
3
  export declare const emailSubjectSchema: () => import("yup").StringSchema<string, import("yup").AnyObject, undefined, "">;
4
4
  export declare const emailContentSchema: () => import("yup").StringSchema<string, import("yup").AnyObject, undefined, "">;
5
5
  export declare const contentTypeSchema: () => import("yup").StringSchema<string, import("yup").AnyObject, undefined, "">;
6
+ export declare const senderNameSchema: () => import("yup").StringSchema<string, import("yup").AnyObject, undefined, "">;
@@ -12,8 +12,10 @@ export const addressOrEnsSchema = () => string()
12
12
  .test('is-address-or-ens', '${path} should be an ethereum address or a ENS name', (value) => isUndefined(value) || isAddressTest(value) || isEnsTest(value));
13
13
  // 78 char length for email subject (rfc2822)
14
14
  export const emailSubjectSchema = () => string().max(78).strict();
15
- // 4096 bytes is the current max length for iExec SMS secrets
16
- export const emailContentSchema = () => string().max(4096).strict();
15
+ // Limit of 512,000 bytes (512 kilo-bytes)
16
+ export const emailContentSchema = () => string().max(512000);
17
17
  // Valid content types for the variable 'contentType'
18
18
  const validContentTypes = ['text/plain', 'text/html'];
19
19
  export const contentTypeSchema = () => string().oneOf(validContentTypes, 'Invalid contentType').optional();
20
+ // Minimum of 3 characters and max of 20 to avoid sender being flagged as spam
21
+ export const senderNameSchema = () => string().trim().min(3).max(20).optional();
@@ -1,2 +1,2 @@
1
1
  import { IExecConsumer, SendEmailParams, SendEmailResponse, SubgraphConsumer } from './types.js';
2
- export declare const sendEmail: ({ graphQLClient, iexec, emailSubject, emailContent, contentType, protectedData, }: IExecConsumer & SubgraphConsumer & SendEmailParams) => Promise<SendEmailResponse>;
2
+ export declare const sendEmail: ({ graphQLClient, iexec, emailSubject, emailContent, contentType, senderName, protectedData, }: IExecConsumer & SubgraphConsumer & SendEmailParams) => Promise<SendEmailResponse>;
@@ -11,8 +11,9 @@ import { DEFAULT_CONTENT_TYPE, MAX_DESIRED_APP_ORDER_PRICE, MAX_DESIRED_WORKERPO
11
11
  import { WorkflowError } from '../utils/errors.js';
12
12
  import { generateSecureUniqueId } from '../utils/generateUniqueId.js';
13
13
  import { checkProtectedDataValidity } from '../utils/subgraphQuery.js';
14
- import { addressOrEnsSchema, contentTypeSchema, emailContentSchema, emailSubjectSchema, throwIfMissing, } from '../utils/validators.js';
15
- export const sendEmail = ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), emailSubject, emailContent, contentType = DEFAULT_CONTENT_TYPE, protectedData, }) => __awaiter(void 0, void 0, void 0, function* () {
14
+ import { addressOrEnsSchema, contentTypeSchema, emailContentSchema, emailSubjectSchema, senderNameSchema, throwIfMissing, } from '../utils/validators.js';
15
+ import * as ipfs from './../utils/ipfs-service.js';
16
+ export const sendEmail = ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), emailSubject, emailContent, contentType = DEFAULT_CONTENT_TYPE, senderName, protectedData, }) => __awaiter(void 0, void 0, void 0, function* () {
16
17
  var _a, _b, _c, _d, _e;
17
18
  try {
18
19
  const vDatasetAddress = addressOrEnsSchema()
@@ -31,6 +32,9 @@ export const sendEmail = ({ graphQLClient = throwIfMissing(), iexec = throwIfMis
31
32
  .required()
32
33
  .label('contentType')
33
34
  .validateSync(contentType);
35
+ const vSenderName = senderNameSchema()
36
+ .label('senderName')
37
+ .validateSync(senderName);
34
38
  // Check protected data validity through subgraph
35
39
  const isValidProtectedData = yield checkProtectedDataValidity(graphQLClient, vDatasetAddress);
36
40
  if (!isValidProtectedData) {
@@ -91,9 +95,22 @@ export const sendEmail = ({ graphQLClient = throwIfMissing(), iexec = throwIfMis
91
95
  const emailContentId = generateSecureUniqueId(16);
92
96
  const optionsId = generateSecureUniqueId(16);
93
97
  yield iexec.secrets.pushRequesterSecret(emailSubjectId, vEmailSubject);
94
- yield iexec.secrets.pushRequesterSecret(emailContentId, vEmailContent);
95
- yield iexec.secrets.pushRequesterSecret(optionsId, JSON.stringify({ contentType: vContentType }));
96
- // Create and sign request order
98
+ const emailContentEncryptionKey = iexec.dataset.generateEncryptionKey();
99
+ const encryptedFile = yield iexec.dataset
100
+ .encrypt(Buffer.from(vEmailContent, 'utf8'), emailContentEncryptionKey)
101
+ .catch((e) => {
102
+ throw new WorkflowError('Failed to encrypt email content', e);
103
+ });
104
+ const cid = yield ipfs.add(encryptedFile).catch((e) => {
105
+ throw new WorkflowError('Failed to upload encrypted email content', e);
106
+ });
107
+ const multiaddr = `/ipfs/${cid}`;
108
+ yield iexec.secrets.pushRequesterSecret(emailContentId, multiaddr);
109
+ yield iexec.secrets.pushRequesterSecret(optionsId, JSON.stringify({
110
+ contentType: vContentType,
111
+ senderName: vSenderName,
112
+ emailContentEncryptionKey,
113
+ }));
97
114
  const requestorderToSign = yield iexec.order.createRequestorder({
98
115
  app: WEB3_MAIL_DAPP_ADDRESS,
99
116
  category: desiredPriceWorkerpoolOrder.category,
@@ -16,6 +16,7 @@ export type SendEmailParams = {
16
16
  emailContent: string;
17
17
  protectedData: Address;
18
18
  contentType?: string;
19
+ senderName?: string;
19
20
  };
20
21
  export type SendEmailResponse = {
21
22
  taskId: Address;
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@iexec/web3mail",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "This product enables users to confidentially store data–such as mail address, documents, personal information ...",
5
- "main": "./dist/bundle.js",
5
+ "main": "./dist/index.js",
6
6
  "type": "module",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "node": "./dist/index.js",
12
- "default": "./dist/bundle.js"
12
+ "default": "./dist/index.js"
13
13
  }
14
14
  },
15
15
  "files": [
@@ -18,8 +18,7 @@
18
18
  "dist"
19
19
  ],
20
20
  "scripts": {
21
- "build": "rm -rf dist && tsc && webpack",
22
- "start": "node dist/bundle.js",
21
+ "build": "rm -rf dist && tsc",
23
22
  "test": "rm -rf dist && tsc && NODE_OPTIONS=--experimental-vm-modules npx jest --coverage",
24
23
  "lint": "eslint . --ext .ts",
25
24
  "format": "prettier --write \"src/**/*.ts\"",
@@ -40,11 +39,8 @@
40
39
  "homepage": "https://github.com/iExecBlockchainComputing/web3mail-sdk#readme",
41
40
  "devDependencies": {
42
41
  "@iexec/dataprotector": "^0.1.0",
43
- "@types/jest": "^29.5.1",
42
+ "@types/jest": "^29.5.4",
44
43
  "@typescript-eslint/eslint-plugin": "^5.54.0",
45
- "assert": "^2.0.0",
46
- "constants-browserify": "^1.0.0",
47
- "crypto-browserify": "^3.12.0",
48
44
  "eslint": "^8.35.0",
49
45
  "eslint-config-prettier": "^8.6.0",
50
46
  "eslint-config-standard-with-typescript": "^34.0.0",
@@ -53,15 +49,16 @@
53
49
  "eslint-plugin-promise": "^6.1.1",
54
50
  "jest": "^29.5.0",
55
51
  "prettier": "^2.8.4",
56
- "stream-browserify": "^3.0.0",
57
52
  "ts-jest": "^29.1.0",
58
53
  "ts-loader": "^9.4.2",
59
- "typescript": "^4.9.5",
60
- "webpack-cli": "^5.0.1"
54
+ "typescript": "^4.9.5"
61
55
  },
62
56
  "dependencies": {
57
+ "@ethersproject/bytes": "^5.7.0",
58
+ "@ethersproject/random": "^5.7.0",
63
59
  "graphql-request": "^6.1.0",
64
- "iexec": "^8.1.5",
60
+ "iexec": "^8.2.1",
61
+ "kubo-rpc-client": "^3.0.1",
65
62
  "yup": "^1.1.1"
66
63
  }
67
64
  }
@@ -5,3 +5,5 @@ export const DATAPROTECTOR_SUBGRAPH_ENDPOINT =
5
5
  export const MAX_DESIRED_APP_ORDER_PRICE = 0;
6
6
  export const MAX_DESIRED_WORKERPOOL_ORDER_PRICE = 0;
7
7
  export const DEFAULT_CONTENT_TYPE = 'text/plain';
8
+ export const IPFS_UPLOAD_URL = '/dns4/ipfs-upload.v8-bellecour.iex.ec/https';
9
+ export const DEFAULT_IPFS_GATEWAY = 'https://ipfs-gateway.v8-bellecour.iex.ec';
@@ -1,6 +1,6 @@
1
- import crypto from 'crypto';
1
+ import { randomBytes } from '@ethersproject/random';
2
+ import { hexlify } from '@ethersproject/bytes';
2
3
 
3
- export function generateSecureUniqueId(length: number): string {
4
- const buffer = crypto.randomBytes(length);
5
- return buffer.toString('hex');
4
+ export function generateSecureUniqueId(length) {
5
+ return hexlify(randomBytes(length));
6
6
  }
@@ -0,0 +1,22 @@
1
+ import { create } from 'kubo-rpc-client';
2
+ import { DEFAULT_IPFS_GATEWAY, IPFS_UPLOAD_URL } from './../config/config.js';
3
+
4
+ const get = async (cid, { ipfsGateway = DEFAULT_IPFS_GATEWAY } = {}) => {
5
+ const multiaddr = `/ipfs/${cid.toString()}`;
6
+ const publicUrl = `${ipfsGateway}${multiaddr}`;
7
+ const res = await fetch(publicUrl);
8
+ if (!res.ok) {
9
+ throw Error(`Failed to load content from ${publicUrl}`);
10
+ }
11
+ const arrayBuffer = await res.arrayBuffer();
12
+ return new Uint8Array(arrayBuffer);
13
+ };
14
+
15
+ const add = async (content, { ipfsGateway = DEFAULT_IPFS_GATEWAY } = {}) => {
16
+ const ipfsClient = create(IPFS_UPLOAD_URL);
17
+ const { cid } = await ipfsClient.add(content);
18
+ await get(cid.toString(), { ipfsGateway });
19
+ return cid.toString();
20
+ };
21
+
22
+ export { add, get };
@@ -23,11 +23,14 @@ export const addressOrEnsSchema = () =>
23
23
  // 78 char length for email subject (rfc2822)
24
24
  export const emailSubjectSchema = () => string().max(78).strict();
25
25
 
26
- // 4096 bytes is the current max length for iExec SMS secrets
27
- export const emailContentSchema = () => string().max(4096).strict();
26
+ // Limit of 512,000 bytes (512 kilo-bytes)
27
+ export const emailContentSchema = () => string().max(512000);
28
28
 
29
29
  // Valid content types for the variable 'contentType'
30
30
  const validContentTypes = ['text/plain', 'text/html'];
31
31
 
32
32
  export const contentTypeSchema = () =>
33
33
  string().oneOf(validContentTypes, 'Invalid contentType').optional();
34
+
35
+ // Minimum of 3 characters and max of 20 to avoid sender being flagged as spam
36
+ export const senderNameSchema = () => string().trim().min(3).max(20).optional();
@@ -13,6 +13,7 @@ import {
13
13
  contentTypeSchema,
14
14
  emailContentSchema,
15
15
  emailSubjectSchema,
16
+ senderNameSchema,
16
17
  throwIfMissing,
17
18
  } from '../utils/validators.js';
18
19
  import {
@@ -21,6 +22,7 @@ import {
21
22
  SendEmailResponse,
22
23
  SubgraphConsumer,
23
24
  } from './types.js';
25
+ import * as ipfs from './../utils/ipfs-service.js';
24
26
 
25
27
  export const sendEmail = async ({
26
28
  graphQLClient = throwIfMissing(),
@@ -28,6 +30,7 @@ export const sendEmail = async ({
28
30
  emailSubject,
29
31
  emailContent,
30
32
  contentType = DEFAULT_CONTENT_TYPE,
33
+ senderName,
31
34
  protectedData,
32
35
  }: IExecConsumer &
33
36
  SubgraphConsumer &
@@ -45,11 +48,13 @@ export const sendEmail = async ({
45
48
  .required()
46
49
  .label('emailContent')
47
50
  .validateSync(emailContent);
48
-
49
51
  const vContentType = contentTypeSchema()
50
52
  .required()
51
53
  .label('contentType')
52
54
  .validateSync(contentType);
55
+ const vSenderName = senderNameSchema()
56
+ .label('senderName')
57
+ .validateSync(senderName);
53
58
 
54
59
  // Check protected data validity through subgraph
55
60
  const isValidProtectedData = await checkProtectedDataValidity(
@@ -137,13 +142,30 @@ export const sendEmail = async ({
137
142
  const emailSubjectId = generateSecureUniqueId(16);
138
143
  const emailContentId = generateSecureUniqueId(16);
139
144
  const optionsId = generateSecureUniqueId(16);
145
+
140
146
  await iexec.secrets.pushRequesterSecret(emailSubjectId, vEmailSubject);
141
- await iexec.secrets.pushRequesterSecret(emailContentId, vEmailContent);
147
+
148
+ const emailContentEncryptionKey = iexec.dataset.generateEncryptionKey();
149
+ const encryptedFile = await iexec.dataset
150
+ .encrypt(Buffer.from(vEmailContent, 'utf8'), emailContentEncryptionKey)
151
+ .catch((e) => {
152
+ throw new WorkflowError('Failed to encrypt email content', e);
153
+ });
154
+ const cid = await ipfs.add(encryptedFile).catch((e) => {
155
+ throw new WorkflowError('Failed to upload encrypted email content', e);
156
+ });
157
+ const multiaddr = `/ipfs/${cid}`;
158
+
159
+ await iexec.secrets.pushRequesterSecret(emailContentId, multiaddr);
142
160
  await iexec.secrets.pushRequesterSecret(
143
161
  optionsId,
144
- JSON.stringify({ contentType: vContentType })
162
+ JSON.stringify({
163
+ contentType: vContentType,
164
+ senderName: vSenderName,
165
+ emailContentEncryptionKey,
166
+ })
145
167
  );
146
- // Create and sign request order
168
+
147
169
  const requestorderToSign = await iexec.order.createRequestorder({
148
170
  app: WEB3_MAIL_DAPP_ADDRESS,
149
171
  category: desiredPriceWorkerpoolOrder.category,
@@ -21,6 +21,7 @@ export type SendEmailParams = {
21
21
  emailContent: string;
22
22
  protectedData: Address;
23
23
  contentType?: string;
24
+ senderName?: string;
24
25
  };
25
26
 
26
27
  export type SendEmailResponse = {