@pinata/sdk 2.0.0-beta.0 → 2.0.0-beta.2
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/lib/pinata-sdk.js +448 -407
- package/lib/pinata-sdk.min.js +5 -5
- package/package.json +2 -2
- package/src/commands/data/pinList/pinList.ts +1 -1
- package/src/commands/pinning/pinFileToIPFS.ts +81 -32
- package/src/commands/pinning/pinFromFS.ts +42 -87
- package/src/util/validators.ts +12 -5
- package/test/commands/pinning/pinFileToIPFS.test.ts +4 -4
- package/types/commands/data/pinList/pinList.d.ts +1 -1
- package/types/commands/pinning/pinFileToIPFS.d.ts +4 -12
- package/types/commands/pinning/pinFromFS.d.ts +2 -2
- package/types/util/validators.d.ts +9 -15
- package/src/third-party/recursive-fs.d.ts +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinata/sdk",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "Official SDK for the Pinata IPFS platform",
|
|
5
5
|
"main": "lib/pinata-sdk.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"base-path-converter": "^1.0.2",
|
|
68
68
|
"form-data": "^2.3.3",
|
|
69
69
|
"is-ipfs": "^0.6.0",
|
|
70
|
-
"
|
|
70
|
+
"path": "^0.12.7"
|
|
71
71
|
},
|
|
72
72
|
"jest": {
|
|
73
73
|
"collectCoverage": true,
|
|
@@ -2,9 +2,13 @@ import axios from 'axios';
|
|
|
2
2
|
import { baseUrl } from '../../constants';
|
|
3
3
|
import NodeFormData from 'form-data';
|
|
4
4
|
import stream from 'stream';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createConfigForAxiosHeadersWithFormData,
|
|
7
|
+
validateMetadata,
|
|
8
|
+
validatePinataOptions
|
|
9
|
+
} from '../../util/validators';
|
|
6
10
|
import { handleError } from '../../util/errorResponse';
|
|
7
|
-
import { PinataConfig } from '../..';
|
|
11
|
+
import { PinataConfig, PinataMetadata } from '../..';
|
|
8
12
|
|
|
9
13
|
export interface PinataPinPolicyItem {
|
|
10
14
|
id: string;
|
|
@@ -12,7 +16,7 @@ export interface PinataPinPolicyItem {
|
|
|
12
16
|
}
|
|
13
17
|
export interface PinataOptions {
|
|
14
18
|
hostNodes?: string[] | undefined;
|
|
15
|
-
cidVersion?: 0 | 1
|
|
19
|
+
cidVersion?: 0 | 1;
|
|
16
20
|
wrapWithDirectory?: boolean;
|
|
17
21
|
customPinPolicy?: {
|
|
18
22
|
regions: PinataPinPolicyItem[];
|
|
@@ -25,45 +29,90 @@ export interface PinataPinResponse {
|
|
|
25
29
|
Timestamp: string;
|
|
26
30
|
}
|
|
27
31
|
export interface PinataPinOptions {
|
|
28
|
-
pinataMetadata?:
|
|
32
|
+
pinataMetadata?: PinataMetadata;
|
|
29
33
|
pinataOptions?: PinataOptions | undefined;
|
|
30
34
|
}
|
|
35
|
+
const endpoint = `${baseUrl}/pinning/pinFileToIPFS`;
|
|
31
36
|
|
|
32
|
-
export
|
|
37
|
+
export function uploadToIPFS(
|
|
38
|
+
config: PinataConfig,
|
|
39
|
+
data: NodeFormData,
|
|
40
|
+
options?: PinataPinOptions
|
|
41
|
+
): Promise<PinataPinResponse> {
|
|
33
42
|
return new Promise((resolve, reject) => {
|
|
43
|
+
if (options && options.pinataMetadata) {
|
|
44
|
+
validateMetadata(options.pinataMetadata);
|
|
45
|
+
data.append(
|
|
46
|
+
'pinataMetadata',
|
|
47
|
+
JSON.stringify(options.pinataMetadata)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (options && options.pinataOptions) {
|
|
51
|
+
validatePinataOptions(options.pinataOptions);
|
|
52
|
+
data.append('pinataOptions', JSON.stringify(options.pinataOptions));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
axios
|
|
56
|
+
.post(
|
|
57
|
+
endpoint,
|
|
58
|
+
data,
|
|
59
|
+
createConfigForAxiosHeadersWithFormData(
|
|
60
|
+
config,
|
|
61
|
+
data.getBoundary()
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
.then(function (result) {
|
|
65
|
+
if (result.status !== 200) {
|
|
66
|
+
reject(
|
|
67
|
+
new Error(
|
|
68
|
+
`unknown server response while pinning File to IPFS: ${result}`
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
resolve(result.data);
|
|
73
|
+
})
|
|
74
|
+
.catch(function (error) {
|
|
75
|
+
const formattedError = handleError(error);
|
|
76
|
+
reject(formattedError);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
34
80
|
|
|
81
|
+
export default function pinFileToIPFS(
|
|
82
|
+
config: PinataConfig,
|
|
83
|
+
readStream: any,
|
|
84
|
+
options?: PinataPinOptions
|
|
85
|
+
): Promise<PinataPinResponse> {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
35
87
|
const data = new NodeFormData();
|
|
36
88
|
|
|
37
|
-
|
|
89
|
+
if (
|
|
90
|
+
!(
|
|
91
|
+
options?.pinataMetadata?.name &&
|
|
92
|
+
typeof options.pinataMetadata.name === 'string' &&
|
|
93
|
+
options.pinataMetadata.name.length > 0
|
|
94
|
+
)
|
|
95
|
+
) {
|
|
96
|
+
throw Error(
|
|
97
|
+
'filename was not provide, make sure to provide options.pinataMetadata.name'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
38
100
|
|
|
39
|
-
|
|
101
|
+
data.append('file', readStream, {
|
|
102
|
+
filename: options.pinataMetadata.name
|
|
103
|
+
});
|
|
40
104
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
105
|
+
if (
|
|
106
|
+
!(
|
|
107
|
+
readStream instanceof stream.Readable ||
|
|
108
|
+
readStream instanceof NodeFormData
|
|
109
|
+
)
|
|
110
|
+
) {
|
|
111
|
+
reject(
|
|
112
|
+
new Error('readStream is not a readable stream or form data')
|
|
113
|
+
);
|
|
43
114
|
}
|
|
44
115
|
|
|
45
|
-
|
|
46
|
-
if (options.pinataMetadata) {
|
|
47
|
-
validateMetadata(options.pinataMetadata);
|
|
48
|
-
data.append('pinataMetadata', JSON.stringify(options.pinataMetadata));
|
|
49
|
-
}
|
|
50
|
-
if (options.pinataOptions) {
|
|
51
|
-
validatePinataOptions(options.pinataOptions);
|
|
52
|
-
data.append('pinataOptions', JSON.stringify(options.pinataOptions));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
axios.post(
|
|
56
|
-
endpoint,
|
|
57
|
-
readStream instanceof NodeFormData ? readStream : data,
|
|
58
|
-
createConfigForAxiosHeadersWithFormData(config, data.getBoundary())
|
|
59
|
-
).then(function (result) {
|
|
60
|
-
if (result.status !== 200) {
|
|
61
|
-
reject(new Error(`unknown server response while pinning File to IPFS: ${result}`));
|
|
62
|
-
}
|
|
63
|
-
resolve(result.data);
|
|
64
|
-
}).catch(function (error) {
|
|
65
|
-
const formattedError = handleError(error);
|
|
66
|
-
reject(formattedError);
|
|
67
|
-
});
|
|
116
|
+
resolve(uploadToIPFS(config, data, options));
|
|
68
117
|
});
|
|
69
118
|
}
|
|
@@ -1,95 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
import { baseUrl } from '../../constants';
|
|
1
|
+
|
|
3
2
|
import NodeFormData from 'form-data';
|
|
4
|
-
|
|
3
|
+
|
|
5
4
|
import basePathConverter from 'base-path-converter';
|
|
6
|
-
import { handleError } from '../../util/errorResponse';
|
|
7
5
|
import { PinataConfig } from '../..';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
createConfigForAxiosHeadersWithFormData(config, data.getBoundary()))
|
|
43
|
-
.then(function (result) {
|
|
44
|
-
if (result.status !== 200) {
|
|
45
|
-
reject(new Error(`unknown server response while pinning File to IPFS: ${result}`));
|
|
46
|
-
}
|
|
47
|
-
resolve(result.data);
|
|
48
|
-
}).catch(function (error) {
|
|
49
|
-
const formattedError = handleError(error);
|
|
50
|
-
reject(formattedError);
|
|
6
|
+
import {
|
|
7
|
+
PinataPinOptions,
|
|
8
|
+
PinataPinResponse,
|
|
9
|
+
uploadToIPFS
|
|
10
|
+
} from './pinFileToIPFS';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
export default async function pinFromFS(
|
|
16
|
+
config: PinataConfig,
|
|
17
|
+
sourcePath: string,
|
|
18
|
+
options?: PinataPinOptions
|
|
19
|
+
): Promise<PinataPinResponse> {
|
|
20
|
+
try {
|
|
21
|
+
const stats = await fs.promises.stat(sourcePath);
|
|
22
|
+
if (stats.isFile()) {
|
|
23
|
+
|
|
24
|
+
const stream = fs.createReadStream(sourcePath);
|
|
25
|
+
const data = new NodeFormData();
|
|
26
|
+
|
|
27
|
+
data.append('file', stream, {
|
|
28
|
+
filename: path.basename(sourcePath)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return uploadToIPFS(config, data, options);
|
|
32
|
+
}
|
|
33
|
+
const files = await fs.promises.readdir(sourcePath);
|
|
34
|
+
const data = new NodeFormData();
|
|
35
|
+
|
|
36
|
+
files.forEach((file: string) => {
|
|
37
|
+
//for each file stream, we need to include the correct relative file path
|
|
38
|
+
data.append('file', fs.createReadStream(file), {
|
|
39
|
+
filepath: basePathConverter(sourcePath, file)
|
|
51
40
|
});
|
|
52
|
-
}
|
|
53
|
-
recursive.readdirr(sourcePath, function (err: any, dirs: any, files: any) {
|
|
54
|
-
if (err) {
|
|
55
|
-
reject(new Error(err));
|
|
56
|
-
}
|
|
41
|
+
});
|
|
57
42
|
|
|
58
|
-
|
|
43
|
+
return uploadToIPFS(config, data, options);
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
});
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.log(err);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
66
49
|
|
|
67
|
-
if (options) {
|
|
68
|
-
if (options.pinataMetadata) {
|
|
69
|
-
validateMetadata(options.pinataMetadata);
|
|
70
|
-
data.append('pinataMetadata', JSON.stringify(options.pinataMetadata));
|
|
71
|
-
}
|
|
72
|
-
if (options.pinataOptions) {
|
|
73
|
-
validatePinataOptions(options.pinataOptions);
|
|
74
|
-
data.append('pinataOptions', JSON.stringify(options.pinataOptions));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
axios.post(
|
|
79
|
-
endpoint,
|
|
80
|
-
data,
|
|
81
|
-
createConfigForAxiosHeadersWithFormData(config, data.getBoundary()))
|
|
82
|
-
.then(function (result) {
|
|
83
|
-
if (result.status !== 200) {
|
|
84
|
-
reject(new Error(`unknown server response while pinning File to IPFS: ${result}`));
|
|
85
|
-
}
|
|
86
|
-
resolve(result.data);
|
|
87
|
-
}).catch(function (error) {
|
|
88
|
-
const formattedError = handleError(error);
|
|
89
|
-
reject(formattedError);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
50
|
}
|
package/src/util/validators.ts
CHANGED
|
@@ -2,6 +2,15 @@ import isIPFS from 'is-ipfs';
|
|
|
2
2
|
import { PinataConfig } from '..';
|
|
3
3
|
import { ERROR_NO_CREDENTIALS_PROVIDED} from '../constants';
|
|
4
4
|
|
|
5
|
+
export interface axiosHeaders
|
|
6
|
+
{ maxContentLength: number;
|
|
7
|
+
maxBodyLength: number;
|
|
8
|
+
headers: {
|
|
9
|
+
[key: string]: any
|
|
10
|
+
};
|
|
11
|
+
withCredentials?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
export function validateApiKeys(pinataApiKey?: string, pinataSecretApiKey?: string) {
|
|
6
15
|
if (!pinataApiKey || pinataApiKey === '') {
|
|
7
16
|
throw new Error(
|
|
@@ -41,15 +50,13 @@ export function createConfigForAxiosHeaders(config: PinataConfig) {
|
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
export function createConfigForAxiosHeadersWithFormData(config: PinataConfig, boundaryValue: string) {
|
|
44
|
-
const requestOptions = {
|
|
53
|
+
const requestOptions: axiosHeaders = {
|
|
45
54
|
...createConfigForAxiosHeaders(config),
|
|
46
55
|
maxContentLength: Infinity, //this is needed to prevent axios from erroring out with large files
|
|
47
|
-
maxBodyLength: Infinity
|
|
48
|
-
headers: {
|
|
49
|
-
'Content-type': `multipart/form-data; boundary= ${boundaryValue}`
|
|
50
|
-
}
|
|
56
|
+
maxBodyLength: Infinity
|
|
51
57
|
};
|
|
52
58
|
|
|
59
|
+
requestOptions.headers['Content-type'] = `multipart/form-data; boundary=${boundaryValue}`;
|
|
53
60
|
return requestOptions;
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -23,7 +23,7 @@ test('200 status is returned with valid stream', () => {
|
|
|
23
23
|
};
|
|
24
24
|
(axios.post as jest.Mock).mockResolvedValue(goodStatus);
|
|
25
25
|
expect.assertions(1);
|
|
26
|
-
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validStream)).resolves.toEqual(goodStatus.data);
|
|
26
|
+
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validStream, {pinataMetadata: { name: 'text.txt'}})).resolves.toEqual(goodStatus.data);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test('200 status is returned with valid form data', () => {
|
|
@@ -33,7 +33,7 @@ test('200 status is returned with valid form data', () => {
|
|
|
33
33
|
};
|
|
34
34
|
(axios.post as jest.Mock).mockResolvedValue(goodStatus);
|
|
35
35
|
expect.assertions(1);
|
|
36
|
-
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validFormData)).resolves.toEqual(goodStatus.data);
|
|
36
|
+
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validFormData, {pinataMetadata: { name: 'text.txt'}})).resolves.toEqual(goodStatus.data);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test('Result other than 200 status is returned', () => {
|
|
@@ -42,12 +42,12 @@ test('Result other than 200 status is returned', () => {
|
|
|
42
42
|
};
|
|
43
43
|
(axios.post as jest.Mock).mockResolvedValue(badStatus);
|
|
44
44
|
expect.assertions(1);
|
|
45
|
-
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validStream)).rejects.toEqual(Error(`unknown server response while pinning File to IPFS: ${badStatus}`));
|
|
45
|
+
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validStream, {pinataMetadata: { name: 'text.txt'}})).rejects.toEqual(Error(`unknown server response while pinning File to IPFS: ${badStatus}`));
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test('Rejection handled', () => {
|
|
49
49
|
(axios.post as jest.Mock).mockRejectedValue('test error');
|
|
50
50
|
expect.assertions(1);
|
|
51
|
-
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validStream)).rejects.toEqual('test error');
|
|
51
|
+
expect(pinFileToIPFS({ pinataApiKey: 'test', pinataSecretApiKey: 'test' }, validStream, {pinataMetadata: { name: 'text.txt'}})).rejects.toEqual('test error');
|
|
52
52
|
});
|
|
53
53
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NodeFormData from "form-data";
|
|
2
|
+
import { PinataConfig, PinataMetadata } from "../..";
|
|
2
3
|
export interface PinataPinPolicyItem {
|
|
3
4
|
id: string;
|
|
4
5
|
desiredReplicationCount: number;
|
|
@@ -17,17 +18,8 @@ export interface PinataPinResponse {
|
|
|
17
18
|
Timestamp: string;
|
|
18
19
|
}
|
|
19
20
|
export interface PinataPinOptions {
|
|
20
|
-
pinataMetadata?:
|
|
21
|
-
[key: string]: string | number | null;
|
|
22
|
-
};
|
|
21
|
+
pinataMetadata?: PinataMetadata;
|
|
23
22
|
pinataOptions?: PinataOptions | undefined;
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
* Pin File to IPFS
|
|
27
|
-
* @param {string} pinataApiKey
|
|
28
|
-
* @param {string} pinataSecretApiKey
|
|
29
|
-
* @param {*} readStream
|
|
30
|
-
* @param {*} options
|
|
31
|
-
* @returns {Promise<unknown>}
|
|
32
|
-
*/
|
|
24
|
+
export declare function uploadToIPFS(config: PinataConfig, data: NodeFormData, options?: PinataPinOptions): Promise<PinataPinResponse>;
|
|
33
25
|
export default function pinFileToIPFS(config: PinataConfig, readStream: any, options?: PinataPinOptions): Promise<PinataPinResponse>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { PinataConfig } from
|
|
2
|
-
import { PinataPinOptions, PinataPinResponse } from
|
|
1
|
+
import { PinataConfig } from "../..";
|
|
2
|
+
import { PinataPinOptions, PinataPinResponse } from "./pinFileToIPFS";
|
|
3
3
|
export default function pinFromFS(config: PinataConfig, sourcePath: string, options?: PinataPinOptions): Promise<PinataPinResponse>;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { PinataConfig } from '..';
|
|
2
|
+
export interface axiosHeaders {
|
|
3
|
+
maxContentLength: number;
|
|
4
|
+
maxBodyLength: number;
|
|
5
|
+
headers: {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
withCredentials?: boolean;
|
|
9
|
+
}
|
|
2
10
|
export declare function validateApiKeys(pinataApiKey?: string, pinataSecretApiKey?: string): void;
|
|
3
11
|
export declare function createConfigForAxiosHeaders(config: PinataConfig): {
|
|
4
12
|
withCredentials: boolean;
|
|
@@ -15,21 +23,7 @@ export declare function createConfigForAxiosHeaders(config: PinataConfig): {
|
|
|
15
23
|
};
|
|
16
24
|
withCredentials?: undefined;
|
|
17
25
|
};
|
|
18
|
-
export declare function createConfigForAxiosHeadersWithFormData(config: PinataConfig, boundaryValue: string):
|
|
19
|
-
maxContentLength: number;
|
|
20
|
-
maxBodyLength: number;
|
|
21
|
-
headers: {
|
|
22
|
-
'Content-type': string;
|
|
23
|
-
};
|
|
24
|
-
withCredentials: boolean;
|
|
25
|
-
} | {
|
|
26
|
-
maxContentLength: number;
|
|
27
|
-
maxBodyLength: number;
|
|
28
|
-
headers: {
|
|
29
|
-
'Content-type': string;
|
|
30
|
-
};
|
|
31
|
-
withCredentials?: undefined;
|
|
32
|
-
};
|
|
26
|
+
export declare function createConfigForAxiosHeadersWithFormData(config: PinataConfig, boundaryValue: string): axiosHeaders;
|
|
33
27
|
export declare function validateHostNodes(hostNodes: any): void;
|
|
34
28
|
export declare function validateMetadata(metadata: any): void;
|
|
35
29
|
export declare function validatePinPolicyStructure(pinPolicy: {
|