@zoralabs/coins-sdk 0.5.1 → 0.5.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/CHANGELOG.md +10 -0
- package/dist/api/queries.d.ts +5 -1
- package/dist/api/queries.d.ts.map +1 -1
- package/dist/client/sdk.gen.d.ts +113 -4
- package/dist/client/sdk.gen.d.ts.map +1 -1
- package/dist/client/types.gen.d.ts +325 -2
- package/dist/client/types.gen.d.ts.map +1 -1
- package/dist/index.cjs +67 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +66 -18
- package/dist/index.js.map +1 -1
- package/dist/uploader/providers/zora.d.ts +6 -3
- package/dist/uploader/providers/zora.d.ts.map +1 -1
- package/dist/uploader/types.d.ts +10 -1
- package/dist/uploader/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/api/queries.ts +22 -0
- package/src/client/sdk.gen.ts +50 -0
- package/src/client/types.gen.ts +335 -2
- package/src/uploader/providers/zora.ts +61 -19
- package/src/uploader/tests/providers.test.ts +169 -49
- package/src/uploader/types.ts +11 -1
|
@@ -10,6 +10,45 @@ global.fetch = mockFetch;
|
|
|
10
10
|
console.log = vi.fn();
|
|
11
11
|
console.error = vi.fn();
|
|
12
12
|
|
|
13
|
+
function mockJwtResponse(jwt = "test-jwt-token") {
|
|
14
|
+
return {
|
|
15
|
+
ok: true,
|
|
16
|
+
headers: new Headers({
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"Content-Length": "1000",
|
|
19
|
+
}),
|
|
20
|
+
json: async () => ({
|
|
21
|
+
createUploadJwtFromApiKey: jwt,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mockUploadResponse(
|
|
27
|
+
cid = "bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
28
|
+
) {
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
status: 200,
|
|
32
|
+
headers: new Headers({
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
}),
|
|
35
|
+
json: async () => ({
|
|
36
|
+
cid,
|
|
37
|
+
size: 100,
|
|
38
|
+
mimeType: "image/png",
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mock401Response() {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
status: 401,
|
|
47
|
+
statusText: "Unauthorized",
|
|
48
|
+
text: async () => "Unauthorized",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
13
52
|
describe("ZoraUploader", () => {
|
|
14
53
|
beforeEach(() => {
|
|
15
54
|
vi.clearAllMocks();
|
|
@@ -31,36 +70,15 @@ describe("ZoraUploader", () => {
|
|
|
31
70
|
|
|
32
71
|
describe("upload", () => {
|
|
33
72
|
it("should successfully upload a file with the zora uploader", async () => {
|
|
34
|
-
// Mock JWT token creation response
|
|
35
73
|
mockFetch
|
|
36
|
-
.mockResolvedValueOnce(
|
|
37
|
-
|
|
38
|
-
headers: new Headers({
|
|
39
|
-
"Content-Type": "application/json",
|
|
40
|
-
"Content-Length": "1000",
|
|
41
|
-
}),
|
|
42
|
-
json: async () => ({
|
|
43
|
-
createUploadJwtFromApiKey: "test-jwt-token",
|
|
44
|
-
}),
|
|
45
|
-
})
|
|
46
|
-
.mockResolvedValueOnce({
|
|
47
|
-
ok: true,
|
|
48
|
-
headers: new Headers({
|
|
49
|
-
"Content-Type": "application/json",
|
|
50
|
-
}),
|
|
51
|
-
json: async () => ({
|
|
52
|
-
cid: "bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
53
|
-
size: 100,
|
|
54
|
-
mimeType: "image/png",
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
74
|
+
.mockResolvedValueOnce(mockJwtResponse())
|
|
75
|
+
.mockResolvedValueOnce(mockUploadResponse());
|
|
57
76
|
|
|
58
77
|
setApiKey("test-api-key");
|
|
59
78
|
const uploader = new ZoraUploader("0x123");
|
|
60
79
|
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
61
80
|
const result = await uploader.upload(file);
|
|
62
81
|
|
|
63
|
-
// Verify fetch was called with correct parameters
|
|
64
82
|
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
65
83
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
66
84
|
"https://ipfs-uploader.zora.co/api/v0/add?cid-version=1",
|
|
@@ -72,7 +90,6 @@ describe("ZoraUploader", () => {
|
|
|
72
90
|
}),
|
|
73
91
|
);
|
|
74
92
|
|
|
75
|
-
// Verify result
|
|
76
93
|
expect(result).toEqual({
|
|
77
94
|
url: "ipfs://bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
78
95
|
size: 100,
|
|
@@ -80,23 +97,65 @@ describe("ZoraUploader", () => {
|
|
|
80
97
|
});
|
|
81
98
|
});
|
|
82
99
|
|
|
83
|
-
it("should throw an error if upload fails", async () => {
|
|
84
|
-
|
|
100
|
+
it("should throw an error if upload fails with non-401 status", async () => {
|
|
101
|
+
mockFetch.mockResolvedValueOnce(mockJwtResponse()).mockResolvedValueOnce({
|
|
102
|
+
ok: false,
|
|
103
|
+
status: 400,
|
|
104
|
+
statusText: "Bad Request",
|
|
105
|
+
text: async () => "Invalid file",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
setApiKey("test-api-key");
|
|
109
|
+
const uploader = new ZoraUploader("0x123");
|
|
110
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
111
|
+
|
|
112
|
+
await expect(uploader.upload(file)).rejects.toThrow(
|
|
113
|
+
"Failed to upload file",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Should NOT retry on non-401 errors
|
|
117
|
+
expect(mockFetch).toHaveBeenCalledTimes(2); // 1 JWT + 1 upload
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should retry once on 401 with a fresh JWT", async () => {
|
|
121
|
+
mockFetch
|
|
122
|
+
// First JWT fetch
|
|
123
|
+
.mockResolvedValueOnce(mockJwtResponse("stale-jwt"))
|
|
124
|
+
// First upload returns 401
|
|
125
|
+
.mockResolvedValueOnce(mock401Response())
|
|
126
|
+
// Second JWT fetch (refresh)
|
|
127
|
+
.mockResolvedValueOnce(mockJwtResponse("fresh-jwt"))
|
|
128
|
+
// Retry upload succeeds
|
|
129
|
+
.mockResolvedValueOnce(mockUploadResponse());
|
|
130
|
+
|
|
131
|
+
setApiKey("test-api-key");
|
|
132
|
+
const uploader = new ZoraUploader("0x123");
|
|
133
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
134
|
+
const result = await uploader.upload(file);
|
|
135
|
+
|
|
136
|
+
expect(mockFetch).toHaveBeenCalledTimes(4); // 2 JWT + 2 upload
|
|
137
|
+
|
|
138
|
+
// Verify the retry used the fresh JWT
|
|
139
|
+
const lastUploadCall = mockFetch.mock.calls[3];
|
|
140
|
+
expect(lastUploadCall[1].headers.Authorization).toBe("Bearer fresh-jwt");
|
|
141
|
+
|
|
142
|
+
expect(result).toEqual({
|
|
143
|
+
url: "ipfs://bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
144
|
+
size: 100,
|
|
145
|
+
mimeType: "image/png",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should throw if retry after 401 also fails", async () => {
|
|
85
150
|
mockFetch
|
|
86
|
-
.mockResolvedValueOnce(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"Content-Type": "application/json",
|
|
90
|
-
"Content-Length": "1000",
|
|
91
|
-
}),
|
|
92
|
-
json: async () => ({
|
|
93
|
-
createUploadJwtFromApiKey: "test-jwt-token",
|
|
94
|
-
}),
|
|
95
|
-
})
|
|
151
|
+
.mockResolvedValueOnce(mockJwtResponse("stale-jwt"))
|
|
152
|
+
.mockResolvedValueOnce(mock401Response())
|
|
153
|
+
.mockResolvedValueOnce(mockJwtResponse("also-bad-jwt"))
|
|
96
154
|
.mockResolvedValueOnce({
|
|
97
155
|
ok: false,
|
|
98
|
-
|
|
99
|
-
|
|
156
|
+
status: 403,
|
|
157
|
+
statusText: "Forbidden",
|
|
158
|
+
text: async () => "Forbidden",
|
|
100
159
|
});
|
|
101
160
|
|
|
102
161
|
setApiKey("test-api-key");
|
|
@@ -107,17 +166,78 @@ describe("ZoraUploader", () => {
|
|
|
107
166
|
"Failed to upload file",
|
|
108
167
|
);
|
|
109
168
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
);
|
|
169
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should reuse cached JWT for subsequent uploads", async () => {
|
|
173
|
+
mockFetch
|
|
174
|
+
.mockResolvedValueOnce(mockJwtResponse())
|
|
175
|
+
.mockResolvedValueOnce(mockUploadResponse())
|
|
176
|
+
.mockResolvedValueOnce(mockUploadResponse());
|
|
177
|
+
|
|
178
|
+
setApiKey("test-api-key");
|
|
179
|
+
const uploader = new ZoraUploader("0x123");
|
|
180
|
+
const file1 = new File(["test1"], "test1.png", { type: "image/png" });
|
|
181
|
+
const file2 = new File(["test2"], "test2.png", { type: "image/png" });
|
|
182
|
+
|
|
183
|
+
await uploader.upload(file1);
|
|
184
|
+
await uploader.upload(file2);
|
|
185
|
+
|
|
186
|
+
// Only 1 JWT fetch + 2 uploads
|
|
187
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should pass signal to upload request", async () => {
|
|
191
|
+
mockFetch
|
|
192
|
+
.mockResolvedValueOnce(mockJwtResponse())
|
|
193
|
+
.mockResolvedValueOnce(mockUploadResponse());
|
|
194
|
+
|
|
195
|
+
setApiKey("test-api-key");
|
|
196
|
+
const uploader = new ZoraUploader("0x123");
|
|
197
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
|
|
200
|
+
await uploader.upload(file, { signal: controller.signal });
|
|
201
|
+
|
|
202
|
+
const uploadCall = mockFetch.mock.calls[1];
|
|
203
|
+
expect(uploadCall[1].signal).toBe(controller.signal);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should pass timeout as AbortSignal to upload request", async () => {
|
|
207
|
+
mockFetch
|
|
208
|
+
.mockResolvedValueOnce(mockJwtResponse())
|
|
209
|
+
.mockResolvedValueOnce(mockUploadResponse());
|
|
210
|
+
|
|
211
|
+
setApiKey("test-api-key");
|
|
212
|
+
const uploader = new ZoraUploader("0x123");
|
|
213
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
214
|
+
|
|
215
|
+
await uploader.upload(file, { timeout: 30_000 });
|
|
216
|
+
|
|
217
|
+
const uploadCall = mockFetch.mock.calls[1];
|
|
218
|
+
expect(uploadCall[1].signal).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should pass combined signal when both signal and timeout are provided", async () => {
|
|
222
|
+
mockFetch
|
|
223
|
+
.mockResolvedValueOnce(mockJwtResponse())
|
|
224
|
+
.mockResolvedValueOnce(mockUploadResponse());
|
|
225
|
+
|
|
226
|
+
setApiKey("test-api-key");
|
|
227
|
+
const uploader = new ZoraUploader("0x123");
|
|
228
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
229
|
+
const controller = new AbortController();
|
|
230
|
+
|
|
231
|
+
await uploader.upload(file, {
|
|
232
|
+
signal: controller.signal,
|
|
233
|
+
timeout: 30_000,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const uploadCall = mockFetch.mock.calls[1];
|
|
237
|
+
// When both signal and timeout are provided, AbortSignal.any() is used
|
|
238
|
+
// so the resulting signal is different from the original
|
|
239
|
+
expect(uploadCall[1].signal).toBeDefined();
|
|
240
|
+
expect(uploadCall[1].signal).not.toBe(controller.signal);
|
|
121
241
|
});
|
|
122
242
|
});
|
|
123
243
|
|
package/src/uploader/types.ts
CHANGED
|
@@ -13,11 +13,21 @@ export type UploadResult = {
|
|
|
13
13
|
mimeType: string | undefined;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Options for file upload operations
|
|
18
|
+
*/
|
|
19
|
+
export type UploadOptions = {
|
|
20
|
+
/** AbortSignal to cancel the upload */
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
/** Timeout in milliseconds for the upload request (no default) */
|
|
23
|
+
timeout?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
16
26
|
/**
|
|
17
27
|
* Interface for file uploaders (IPFS, Arweave, etc.)
|
|
18
28
|
*/
|
|
19
29
|
export interface Uploader {
|
|
20
|
-
upload(file: File): Promise<UploadResult>;
|
|
30
|
+
upload(file: File, options?: UploadOptions): Promise<UploadResult>;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export type CreateMetadataParameters = {
|