rezo 1.0.29 → 1.0.31

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 (63) hide show
  1. package/dist/adapters/curl.cjs +8 -10
  2. package/dist/adapters/curl.js +8 -10
  3. package/dist/adapters/entries/curl.d.ts +169 -313
  4. package/dist/adapters/entries/fetch.d.ts +169 -313
  5. package/dist/adapters/entries/http.d.ts +169 -313
  6. package/dist/adapters/entries/http2.d.ts +169 -313
  7. package/dist/adapters/entries/react-native.d.ts +169 -313
  8. package/dist/adapters/entries/xhr.d.ts +169 -313
  9. package/dist/adapters/fetch.cjs +10 -7
  10. package/dist/adapters/fetch.js +10 -7
  11. package/dist/adapters/http.cjs +9 -12
  12. package/dist/adapters/http.js +9 -12
  13. package/dist/adapters/http2.cjs +6 -11
  14. package/dist/adapters/http2.js +6 -11
  15. package/dist/adapters/index.cjs +6 -6
  16. package/dist/adapters/react-native.cjs +4 -4
  17. package/dist/adapters/react-native.js +4 -4
  18. package/dist/adapters/xhr.cjs +4 -4
  19. package/dist/adapters/xhr.js +4 -4
  20. package/dist/cache/index.cjs +13 -13
  21. package/dist/cache/universal-response-cache.cjs +156 -0
  22. package/dist/cache/universal-response-cache.js +155 -0
  23. package/dist/core/rezo.cjs +2 -8
  24. package/dist/core/rezo.js +2 -8
  25. package/dist/crawler.d.ts +163 -313
  26. package/dist/entries/crawler.cjs +5 -5
  27. package/dist/index.cjs +24 -24
  28. package/dist/index.d.ts +169 -313
  29. package/dist/platform/browser.d.ts +169 -313
  30. package/dist/platform/bun.d.ts +169 -313
  31. package/dist/platform/deno.d.ts +169 -313
  32. package/dist/platform/node.d.ts +169 -313
  33. package/dist/platform/react-native.d.ts +169 -313
  34. package/dist/platform/worker.d.ts +169 -313
  35. package/dist/plugin/crawler.cjs +1 -1
  36. package/dist/plugin/crawler.js +1 -1
  37. package/dist/plugin/index.cjs +36 -36
  38. package/dist/proxy/index.cjs +5 -80
  39. package/dist/proxy/index.js +2 -77
  40. package/dist/proxy/parse.cjs +79 -0
  41. package/dist/proxy/parse.js +77 -0
  42. package/dist/queue/index.cjs +8 -8
  43. package/dist/responses/buildResponse.cjs +15 -15
  44. package/dist/responses/buildResponse.js +15 -15
  45. package/dist/responses/universal/download.cjs +23 -0
  46. package/dist/responses/universal/download.js +22 -0
  47. package/dist/responses/universal/event-emitter.cjs +104 -0
  48. package/dist/responses/universal/event-emitter.js +102 -0
  49. package/dist/responses/universal/index.cjs +11 -0
  50. package/dist/responses/universal/index.js +4 -0
  51. package/dist/responses/universal/stream.cjs +32 -0
  52. package/dist/responses/universal/stream.js +31 -0
  53. package/dist/responses/universal/upload.cjs +23 -0
  54. package/dist/responses/universal/upload.js +22 -0
  55. package/dist/utils/cookies.browser.cjs +63 -0
  56. package/dist/utils/cookies.browser.js +61 -0
  57. package/dist/utils/form-data.cjs +212 -189
  58. package/dist/utils/form-data.js +212 -189
  59. package/dist/utils/http-config.cjs +41 -21
  60. package/dist/utils/http-config.js +41 -21
  61. package/package.json +11 -4
  62. package/dist/types/cookies.cjs +0 -394
  63. package/dist/types/cookies.js +0 -391
@@ -1,218 +1,241 @@
1
- import NodeFormData from "form-data";
2
- function isReadableStream(value) {
3
- return value !== null && typeof value === "object" && typeof value.pipe === "function" && typeof value.read === "function" && typeof value.on === "function";
1
+ const hasBuffer = typeof Buffer !== "undefined";
2
+ function isBuffer(value) {
3
+ return hasBuffer && Buffer.isBuffer(value);
4
+ }
5
+ function toBlob(value, contentType) {
6
+ const options = contentType ? { type: contentType } : undefined;
7
+ const copy = new ArrayBuffer(value.byteLength);
8
+ new Uint8Array(copy).set(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
9
+ return new Blob([copy], options);
4
10
  }
5
11
 
6
- export class RezoFormData extends NodeFormData {
7
- constructor(options) {
8
- super(options);
9
- }
10
- async getFieldEntries() {
11
- return new Promise((resolve, reject) => {
12
- const entries = [];
13
- const fields = this._fields || [];
14
- for (const field of fields) {
15
- if (field && field.name && field.value !== undefined) {
16
- entries.push([field.name, field.value]);
17
- }
18
- }
19
- resolve(entries);
20
- });
12
+ export class RezoFormData {
13
+ _fd;
14
+ _cachedContentType = null;
15
+ _cachedBuffer = null;
16
+ constructor() {
17
+ this._fd = new FormData;
21
18
  }
22
- async toNativeFormData() {
23
- if (typeof globalThis !== "undefined" && typeof globalThis.FormData !== "undefined" || typeof global !== "undefined" && typeof global.FormData !== "undefined" || typeof window !== "undefined" && typeof window.FormData !== "undefined") {
24
- const formData = new FormData;
25
- const entries = await this.getFieldEntries();
26
- for (const [key, value] of entries) {
27
- if (isReadableStream(value)) {
28
- const chunks = [];
29
- for await (const chunk of value) {
30
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
31
- }
32
- const buffer = Buffer.concat(chunks);
33
- const blob = new Blob([buffer]);
34
- formData.append(key, blob);
35
- } else {
36
- formData.append(key, value);
37
- }
19
+ append(name, value, filename) {
20
+ this._invalidateCache();
21
+ if (isBuffer(value)) {
22
+ const blob = toBlob(value);
23
+ if (filename) {
24
+ this._fd.append(name, blob, filename);
25
+ } else {
26
+ this._fd.append(name, blob);
38
27
  }
39
- return formData;
28
+ } else if (filename && value instanceof Blob) {
29
+ this._fd.append(name, value, filename);
30
+ } else {
31
+ this._fd.append(name, value);
40
32
  }
41
- return null;
42
- }
43
- static async fromNativeFormData(formData, options) {
44
- const rezoFormData = new RezoFormData(options);
45
- for (const [key, value] of Array.from(formData.entries())) {
46
- if (typeof File !== "undefined" && value instanceof File) {
47
- const file = value;
48
- const arrayBuffer = await file.arrayBuffer();
49
- const buffer = Buffer.from(arrayBuffer);
50
- rezoFormData.append(key, buffer, {
51
- filename: file.name,
52
- contentType: file.type || "application/octet-stream"
53
- });
54
- } else if (typeof Blob !== "undefined" && value instanceof Blob) {
55
- const blob = value;
56
- const arrayBuffer = await blob.arrayBuffer();
57
- const buffer = Buffer.from(arrayBuffer);
58
- rezoFormData.append(key, buffer, {
59
- contentType: blob.type || "application/octet-stream"
60
- });
33
+ }
34
+ set(name, value, filename) {
35
+ this._invalidateCache();
36
+ if (isBuffer(value)) {
37
+ const blob = toBlob(value);
38
+ if (filename) {
39
+ this._fd.set(name, blob, filename);
61
40
  } else {
62
- rezoFormData.append(key, value);
41
+ this._fd.set(name, blob);
63
42
  }
43
+ } else if (filename && value instanceof Blob) {
44
+ this._fd.set(name, value, filename);
45
+ } else {
46
+ this._fd.set(name, value);
64
47
  }
65
- return rezoFormData;
66
48
  }
67
- getContentType() {
68
- return `multipart/form-data; boundary=${this.getBoundary()}`;
49
+ get(name) {
50
+ return this._fd.get(name);
69
51
  }
70
- toBuffer() {
71
- return Buffer.from(this.toString());
52
+ getAll(name) {
53
+ return this._fd.getAll(name);
72
54
  }
73
- static fromObject(obj, options) {
74
- const formData = new RezoFormData(options);
75
- for (const [key, value] of Object.entries(obj)) {
76
- RezoFormData.appendValue(formData, key, value);
77
- }
78
- return formData;
55
+ has(name) {
56
+ return this._fd.has(name);
79
57
  }
80
- static appendValue(formData, key, value) {
81
- if (value === null || value === undefined) {
82
- return;
83
- }
84
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
85
- formData.append(key, String(value));
86
- return;
87
- }
88
- if (Buffer.isBuffer(value)) {
89
- formData.append(key, value, {
90
- contentType: "application/octet-stream"
91
- });
92
- return;
93
- }
94
- if (value instanceof Uint8Array) {
95
- formData.append(key, Buffer.from(value), {
96
- contentType: "application/octet-stream"
97
- });
98
- return;
99
- }
100
- if (isReadableStream(value)) {
101
- formData.append(key, value);
102
- return;
103
- }
104
- if (typeof File !== "undefined" && value instanceof File) {
105
- formData.append(key, value, {
106
- filename: value.name,
107
- contentType: value.type || "application/octet-stream"
108
- });
109
- return;
110
- }
111
- if (typeof Blob !== "undefined" && value instanceof Blob) {
112
- formData.append(key, value, {
113
- contentType: value.type || "application/octet-stream"
114
- });
115
- return;
116
- }
117
- if (Array.isArray(value)) {
118
- for (const item of value) {
119
- RezoFormData.appendValue(formData, key, item);
120
- }
121
- return;
58
+ delete(name) {
59
+ this._invalidateCache();
60
+ this._fd.delete(name);
61
+ }
62
+ entries() {
63
+ return this._fd.entries();
64
+ }
65
+ keys() {
66
+ return this._fd.keys();
67
+ }
68
+ values() {
69
+ return this._fd.values();
70
+ }
71
+ forEach(callback) {
72
+ this._fd.forEach(callback);
73
+ }
74
+ [Symbol.iterator]() {
75
+ return this._fd.entries();
76
+ }
77
+ toNativeFormData() {
78
+ return this._fd;
79
+ }
80
+ _invalidateCache() {
81
+ this._cachedContentType = null;
82
+ this._cachedBuffer = null;
83
+ }
84
+ async _buildResponse() {
85
+ if (this._cachedBuffer === null || this._cachedContentType === null) {
86
+ const response = new Response(this._fd);
87
+ this._cachedContentType = response.headers.get("content-type") || "multipart/form-data";
88
+ this._cachedBuffer = await response.arrayBuffer();
89
+ }
90
+ return new Response(this._cachedBuffer, {
91
+ headers: { "content-type": this._cachedContentType }
92
+ });
93
+ }
94
+ getBoundary() {
95
+ if (!this._cachedContentType) {
96
+ return "";
122
97
  }
123
- if (typeof value === "object" && value !== null && "value" in value && (value.filename || value.contentType)) {
124
- const opts = {};
125
- if (value.filename)
126
- opts.filename = value.filename;
127
- if (value.contentType)
128
- opts.contentType = value.contentType;
129
- formData.append(key, value.value, opts);
130
- return;
98
+ const match = this._cachedContentType.match(/boundary=([^;]+)/);
99
+ return match ? match[1] : "";
100
+ }
101
+ getContentType() {
102
+ return this._cachedContentType || undefined;
103
+ }
104
+ async getContentTypeAsync() {
105
+ await this._buildResponse();
106
+ return this._cachedContentType;
107
+ }
108
+ getHeaders() {
109
+ if (this._cachedContentType) {
110
+ return { "content-type": this._cachedContentType };
131
111
  }
132
- if (typeof value === "object" && value !== null) {
133
- const jsonString = JSON.stringify(value);
134
- const jsonBuffer = Buffer.from(jsonString, "utf8");
135
- formData.append(key, jsonBuffer, {
136
- filename: `${key}.json`,
137
- contentType: "application/json"
138
- });
139
- return;
112
+ return {};
113
+ }
114
+ async getHeadersAsync() {
115
+ const contentType = await this.getContentTypeAsync();
116
+ const length = await this.getLength();
117
+ return {
118
+ "content-type": contentType,
119
+ "content-length": String(length)
120
+ };
121
+ }
122
+ getLengthSync() {
123
+ return this._cachedBuffer?.byteLength;
124
+ }
125
+ async getLength() {
126
+ await this._buildResponse();
127
+ return this._cachedBuffer.byteLength;
128
+ }
129
+ getBuffer() {
130
+ if (!hasBuffer || !this._cachedBuffer) {
131
+ return null;
140
132
  }
141
- formData.append(key, String(value));
133
+ return Buffer.from(this._cachedBuffer);
142
134
  }
143
- async toUrlQueryString(convertBinaryToBase64 = false) {
144
- const params = new URLSearchParams;
145
- let hasOmittedData = false;
146
- try {
147
- const entries = await this.getFieldEntries();
148
- for (const [name, value] of entries) {
149
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
150
- params.append(name, String(value));
151
- } else if (value instanceof Buffer) {
152
- if (convertBinaryToBase64) {
153
- params.append(name, value.toString("base64"));
154
- } else {
155
- hasOmittedData = true;
156
- }
157
- } else if (isReadableStream(value) || typeof File !== "undefined" && value instanceof File || typeof Blob !== "undefined" && value instanceof Blob) {
158
- if (convertBinaryToBase64 && value instanceof File) {
159
- hasOmittedData = true;
160
- } else {
161
- hasOmittedData = true;
162
- }
163
- } else if (typeof value === "object" && value && "value" in value) {
164
- if (typeof value.value === "string" || typeof value.value === "number" || typeof value.value === "boolean") {
165
- params.append(name, String(value.value));
135
+ async toBuffer() {
136
+ await this._buildResponse();
137
+ return Buffer.from(this._cachedBuffer);
138
+ }
139
+ async toArrayBuffer() {
140
+ await this._buildResponse();
141
+ return this._cachedBuffer;
142
+ }
143
+ async toUint8Array() {
144
+ await this._buildResponse();
145
+ return new Uint8Array(this._cachedBuffer);
146
+ }
147
+ static fromObject(obj) {
148
+ const fd = new RezoFormData;
149
+ const appendValue = (key, value) => {
150
+ if (value === null || value === undefined) {
151
+ return;
152
+ }
153
+ if (typeof value === "string") {
154
+ fd.append(key, value);
155
+ return;
156
+ }
157
+ if (typeof value === "number" || typeof value === "boolean") {
158
+ fd.append(key, String(value));
159
+ return;
160
+ }
161
+ if (value instanceof Blob) {
162
+ const filename = value instanceof File ? value.name : undefined;
163
+ fd.append(key, value, filename);
164
+ return;
165
+ }
166
+ if (isBuffer(value)) {
167
+ fd.append(key, toBlob(value));
168
+ return;
169
+ }
170
+ if (value instanceof Uint8Array) {
171
+ fd.append(key, toBlob(value));
172
+ return;
173
+ }
174
+ if (value instanceof ArrayBuffer) {
175
+ fd.append(key, new Blob([value]));
176
+ return;
177
+ }
178
+ if (Array.isArray(value)) {
179
+ for (let i = 0;i < value.length; i++) {
180
+ appendValue(`${key}[${i}]`, value[i]);
181
+ }
182
+ return;
183
+ }
184
+ if (typeof value === "object" && value !== null) {
185
+ if ("value" in value && (("filename" in value) || ("contentType" in value))) {
186
+ const v = value;
187
+ if (v.value instanceof Blob) {
188
+ fd.append(key, v.value, v.filename);
189
+ } else if (isBuffer(v.value)) {
190
+ const blob = toBlob(v.value, v.contentType);
191
+ fd.append(key, blob, v.filename);
192
+ } else if (v.value instanceof Uint8Array) {
193
+ const blob = toBlob(v.value, v.contentType);
194
+ fd.append(key, blob, v.filename);
166
195
  } else {
167
- hasOmittedData = true;
196
+ fd.append(key, String(v.value));
168
197
  }
169
- } else {
170
- hasOmittedData = true;
198
+ return;
171
199
  }
200
+ for (const [subKey, subValue] of Object.entries(value)) {
201
+ appendValue(`${key}[${subKey}]`, subValue);
202
+ }
203
+ return;
172
204
  }
173
- } catch (error) {
174
- console.warn("RezoFormData.toUrlQueryString(): Error reading form data entries:", error);
205
+ fd.append(key, String(value));
206
+ };
207
+ for (const [key, value] of Object.entries(obj)) {
208
+ appendValue(key, value);
175
209
  }
176
- if (hasOmittedData && !convertBinaryToBase64) {
177
- console.warn("RezoFormData.toUrlQueryString(): Binary data, files, and blobs have been omitted. Use convertBinaryToBase64=true to include binary data as base64 strings.");
210
+ return fd;
211
+ }
212
+ static fromNativeFormData(formData) {
213
+ const fd = new RezoFormData;
214
+ for (const [key, value] of formData.entries()) {
215
+ if (typeof value === "string") {
216
+ fd.append(key, value);
217
+ } else {
218
+ const filename = value.name || undefined;
219
+ fd.append(key, value, filename);
220
+ }
178
221
  }
179
- return params.toString();
222
+ return fd;
180
223
  }
181
- async toURLSearchParams(convertBinaryToBase64 = false) {
224
+ toUrlQueryString() {
182
225
  const params = new URLSearchParams;
183
- let hasOmittedData = false;
184
- try {
185
- const entries = await this.getFieldEntries();
186
- for (const [name, value] of entries) {
187
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
188
- params.append(name, String(value));
189
- } else if (value instanceof Buffer) {
190
- if (convertBinaryToBase64) {
191
- params.append(name, value.toString("base64"));
192
- } else {
193
- hasOmittedData = true;
194
- }
195
- } else if (isReadableStream(value) || typeof File !== "undefined" && value instanceof File || typeof Blob !== "undefined" && value instanceof Blob) {
196
- if (convertBinaryToBase64 && value instanceof File) {
197
- hasOmittedData = true;
198
- } else {
199
- hasOmittedData = true;
200
- }
201
- } else if (typeof value === "object" && value && "value" in value) {
202
- if (typeof value.value === "string" || typeof value.value === "number" || typeof value.value === "boolean") {
203
- params.append(name, String(value.value));
204
- } else {
205
- hasOmittedData = true;
206
- }
207
- } else {
208
- hasOmittedData = true;
209
- }
226
+ for (const [key, value] of this._fd.entries()) {
227
+ if (typeof value === "string") {
228
+ params.append(key, value);
210
229
  }
211
- } catch (error) {
212
- console.warn("RezoFormData.toURLSearchParams(): Error reading form data entries:", error);
213
230
  }
214
- if (hasOmittedData && !convertBinaryToBase64) {
215
- console.warn("RezoFormData.toURLSearchParams(): Binary data, files, and blobs have been omitted. Use convertBinaryToBase64=true to include binary data as base64 strings.");
231
+ return params.toString();
232
+ }
233
+ toURLSearchParams() {
234
+ const params = new URLSearchParams;
235
+ for (const [key, value] of this._fd.entries()) {
236
+ if (typeof value === "string") {
237
+ params.append(key, value);
238
+ }
216
239
  }
217
240
  return params;
218
241
  }
@@ -2,9 +2,12 @@ const { RezoCookieJar } = require('./cookies.cjs');
2
2
  const RezoFormData = require('./form-data.cjs');
3
3
  const { RezoHeaders } = require('./headers.cjs');
4
4
  const { RezoURLSearchParams } = require('./data-operations.cjs');
5
- const path = require("node:path");
6
- const { parseProxyString } = require('../proxy/index.cjs');
5
+ const { parseProxyString } = require('../proxy/parse.cjs');
7
6
  const { createDefaultHooks, mergeHooks, serializeHooks } = require('../core/hooks.cjs');
7
+ const hasBuffer = typeof Buffer !== "undefined";
8
+ function isBuffer(value) {
9
+ return hasBuffer && Buffer.isBuffer(value);
10
+ }
8
11
  const ERROR_INFO = exports.ERROR_INFO = {
9
12
  ECONNREFUSED: {
10
13
  code: -111,
@@ -314,7 +317,6 @@ function prepareHTTPOptions(options, jar, addedOptions, config) {
314
317
  if (options.formData || options.multipart) {
315
318
  if (options.multipart instanceof RezoFormData || options.formData instanceof RezoFormData) {
316
319
  const body = options.multipart instanceof RezoFormData ? options.multipart : options.formData;
317
- contentType = body.getContentType();
318
320
  fetchOptions.body = body;
319
321
  } else {
320
322
  const body = new RezoFormData;
@@ -323,27 +325,31 @@ function prepareHTTPOptions(options, jar, addedOptions, config) {
323
325
  Object.entries(_body).forEach(([key, value]) => {
324
326
  if (value === null || value === undefined) {
325
327
  body.append(key, "");
326
- } else if (typeof value === "string" || Buffer.isBuffer(value)) {
328
+ } else if (typeof value === "string") {
329
+ body.append(key, value);
330
+ } else if (isBuffer(value)) {
327
331
  body.append(key, value);
328
- } else if (typeof value === "object" && typeof value.pipe === "function") {
332
+ } else if (value instanceof Blob) {
329
333
  body.append(key, value);
330
334
  } else if (typeof value === "object" && value.value !== undefined) {
331
335
  const val = value.value;
332
- const opts = value.options || {};
333
- if (typeof val === "string" || Buffer.isBuffer(val)) {
334
- body.append(key, val, opts);
336
+ const filename = value.options?.filename || value.filename;
337
+ if (typeof val === "string") {
338
+ body.append(key, val);
339
+ } else if (isBuffer(val)) {
340
+ body.append(key, val, filename);
341
+ } else if (val instanceof Blob) {
342
+ body.append(key, val, filename);
335
343
  } else {
336
- body.append(key, String(val), opts);
344
+ body.append(key, String(val));
337
345
  }
338
346
  } else {
339
347
  body.append(key, String(value));
340
348
  }
341
349
  });
342
350
  }
343
- contentType = body.getContentType();
344
351
  fetchOptions.body = body;
345
352
  }
346
- fetchOptions.headers.set("Content-Type", contentType);
347
353
  } else if (options.form) {
348
354
  contentType = "application/x-www-form-urlencoded";
349
355
  if (typeof options.form === "object") {
@@ -375,17 +381,23 @@ function prepareHTTPOptions(options, jar, addedOptions, config) {
375
381
  Object.entries(fetchOptions.body).forEach(([key, value]) => {
376
382
  if (value === null || value === undefined) {
377
383
  formData.append(key, "");
378
- } else if (typeof value === "string" || Buffer.isBuffer(value)) {
384
+ } else if (typeof value === "string") {
379
385
  formData.append(key, value);
380
- } else if (typeof value === "object" && typeof value.pipe === "function") {
386
+ } else if (isBuffer(value)) {
387
+ formData.append(key, value);
388
+ } else if (value instanceof Blob) {
381
389
  formData.append(key, value);
382
390
  } else if (typeof value === "object" && value.value !== undefined) {
383
391
  const val = value.value;
384
- const opts = value.options || {};
385
- if (typeof val === "string" || Buffer.isBuffer(val)) {
386
- formData.append(key, val, opts);
392
+ const filename = value.options?.filename || value.filename;
393
+ if (typeof val === "string") {
394
+ formData.append(key, val);
395
+ } else if (isBuffer(val)) {
396
+ formData.append(key, val, filename);
397
+ } else if (val instanceof Blob) {
398
+ formData.append(key, val, filename);
387
399
  } else {
388
- formData.append(key, String(val), opts);
400
+ formData.append(key, String(val));
389
401
  }
390
402
  } else {
391
403
  formData.append(key, String(value));
@@ -642,13 +654,21 @@ As a workaround, process.env.NODE_TLS_REJECT_UNAUTHORIZED is being set to '0'.
642
654
  } else if (!fs) {
643
655
  throw new Error(`You can only use this feature in nodejs module, not in Edge module.`);
644
656
  }
645
- const name = path.basename(saveTo);
646
- if (checkISPermission && checkISPermission(saveTo.length ? path.dirname(saveTo) : path.resolve(process.cwd()), fs)) {
647
- const dir = name.length < saveTo.length ? path.dirname(saveTo) : path.join(process.cwd(), "download");
657
+ const basename = (p) => p.split(/[/\\]/).pop() || "";
658
+ const dirname = (p) => {
659
+ const parts = p.split(/[/\\]/);
660
+ parts.pop();
661
+ return parts.join("/") || ".";
662
+ };
663
+ const join = (...parts) => parts.join("/").replace(/\/+/g, "/");
664
+ const name = basename(saveTo);
665
+ const cwd = typeof process !== "undefined" && process.cwd ? process.cwd() : ".";
666
+ if (checkISPermission && checkISPermission(saveTo.length ? dirname(saveTo) : cwd, fs)) {
667
+ const dir = name.length < saveTo.length ? dirname(saveTo) : join(cwd, "download");
648
668
  if (!fs.existsSync(dir)) {
649
669
  fs.mkdirSync(dir, { recursive: true });
650
670
  }
651
- fileName = path.join(dir, name);
671
+ fileName = join(dir, name);
652
672
  config.fileName = fileName;
653
673
  } else {
654
674
  throw new Error(`Permission denied to save to ${saveTo}`);