minifetch-api 0.0.1 → 1.0.1
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/LICENSE +21 -0
- package/README.md +112 -16
- package/dist/esm/client.js +266 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/init.js +70 -0
- package/dist/esm/types/config.js +1 -0
- package/dist/esm/types/errors.js +122 -0
- package/dist/esm/types/responses.js +1 -0
- package/dist/esm/utils/payment.js +98 -0
- package/dist/esm/utils/validation.js +98 -0
- package/dist/types/client.d.ts +103 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/init.d.ts +26 -0
- package/dist/types/types/config.d.ts +21 -0
- package/dist/types/types/errors.d.ts +86 -0
- package/dist/types/types/responses.d.ts +50 -0
- package/dist/types/utils/payment.d.ts +16 -0
- package/dist/types/utils/validation.d.ts +8 -0
- package/package.json +62 -13
- package/.env-local +0 -10
- package/.prettierignore +0 -8
- package/.prettierrc +0 -11
- package/.tsconfig.json +0 -15
- package/eslint.config.js +0 -72
- package/index.ts +0 -88
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lauren Garcia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,26 +1,122 @@
|
|
|
1
|
-
# Minifetch
|
|
1
|
+
# Minifetch API
|
|
2
|
+
<div align=center>
|
|
3
|
+
<a href="https://minifetch.com">
|
|
4
|
+
<img src="https://minifetch.com/minifetch-dog-logo--whitebg.png" width="70" />
|
|
5
|
+
</a>
|
|
6
|
+
</div>
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
**Fetch & extract data from web pages.** [Minifetch.com](https://minifetch.com) provides composable extraction APIs that humans and AI Agents can autonomously discover, orchestrate, and pay for— making web pages simple to access. Pay-as-you-go at competitive prices.
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
**Full [API docs are here](https://minifetch.com/docs/api)**, including example response data & prices. For AI Agents, read the [LLMs.txt](https://minifetch.com/llms.txt).
|
|
11
|
+
|
|
12
|
+
**Payments** work with [x402](https://www.x402.org/) USDC stablecoin micropayments on Coinbase's Base & Solana blockchain networks. Transaction fees are free. If you'd like to use a traditional credit card & API key instead, sign up for the waitlist and we'll let you know when it's ready: [forms.gle/rkMi7T23bHJc8XFw9](https://forms.gle/rkMi7T23bHJc8XFw9)
|
|
13
|
+
|
|
14
|
+
**Bulk fetch and extract** for building datasets is coming soon, sign up for the waitlist and we'll let you know when it's ready: [forms.gle/rkMi7T23bHJc8XFw9](https://forms.gle/rkMi7T23bHJc8XFw9)
|
|
6
15
|
|
|
7
16
|
## Prerequisites
|
|
8
17
|
|
|
9
|
-
- Node.js
|
|
10
|
-
-
|
|
11
|
-
- A valid Ethereum private key for making USDC payments on Base Sepolia Testnet (only) -- live production client coming soon!
|
|
18
|
+
- Node.js v18+ & NPM
|
|
19
|
+
- A valid Ethereum or Solana private key for making USDC payments.
|
|
12
20
|
|
|
13
|
-
##
|
|
21
|
+
## Install
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
23
|
+
`npm install minifetch-api --save`
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
```js
|
|
28
|
+
import Minifetch from "minifetch-api";
|
|
29
|
+
|
|
30
|
+
// Inititialize the client with your network choice & private key.
|
|
31
|
+
// - Network options: "base" or "solana"
|
|
32
|
+
// - Use private key from account that has a small amt of USDC
|
|
33
|
+
const client = new Minifetch({
|
|
34
|
+
network: "base",
|
|
35
|
+
privateKey: process.env.BASE_PRIVATE_KEY,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const url = "example.com";
|
|
40
|
+
const response = await client.checkAndExtractPreview(url);
|
|
41
|
+
// 200 "ok" responses will return the data and x402 payment info:
|
|
42
|
+
console.log("data: ", response.data);
|
|
43
|
+
console.log("payment info: ", response.payment);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// No payment or charges for errors!
|
|
46
|
+
console.log(err);
|
|
47
|
+
}
|
|
25
48
|
```
|
|
26
49
|
|
|
50
|
+
### Client Methods Available
|
|
51
|
+
|
|
52
|
+
After you initialize the client (above), you have the following methods available to use. The "checkAndExtract" API methods provided help you avoid paying for blocked URLs and return more granular info about why a URL may or may not return data. There is no charge for blocked upstream URLs.
|
|
53
|
+
|
|
54
|
+
[Price list and example data for each method is here.](https://minifetch.com/docs/api#example-data)
|
|
55
|
+
|
|
56
|
+
Requests exceeding rate limits return `503` errors. This is intentional — back off and retry, max 5-10 queries per second. We'll add bulk fetch and extract in the future. Sign up for the [waitlist here](https://forms.gle/rkMi7T23bHJc8XFw9).
|
|
57
|
+
|
|
58
|
+
- **`client.checkAndExtractUrlPreview(url)`**
|
|
59
|
+
- Extracts a light, token-efficient preview of a URL: title, description, and image (only).
|
|
60
|
+
|
|
61
|
+
- **`client.checkAndExtractUrlContent(url, options)`**
|
|
62
|
+
- Extracts a clean, token-efficient content summary as markdown from a URL. Removes ads, nav, scripts. Much more efficient than raw HTML fetches for LLMs.
|
|
63
|
+
- Options: `{ includeMediaUrls: true }`, defaults to false
|
|
64
|
+
|
|
65
|
+
- **`client.checkAndExtractUrlLinks(url)`**
|
|
66
|
+
- Extracts all links from a URL categorized by type (internal/external/anchor) with SEO metadata. Detects image links, nofollow attributes, and analyzes external domain distribution.
|
|
67
|
+
|
|
68
|
+
- **`client.checkAndExtractUrlMetadata(url, options)`**
|
|
69
|
+
- Extracts rich structured metadata from a URL: title, description, og/twitter tags, json-ld, images, headings, response headers, and more. Perfect for SEO research or metadata indexing & analysis. Largest response size of the client methods available.
|
|
70
|
+
- Options: `{ includeResponseBody: true }`, defaults to false
|
|
71
|
+
|
|
72
|
+
For maximum control, you can also use the following methods directly:
|
|
73
|
+
|
|
74
|
+
- **`client.preflightCheck(url)`**
|
|
75
|
+
- Standalone call to check if a URL is allowed to be fetched according to the website's robots.txt file. Use this free endpoint before using our other paid endpoints to avoid spending extra on un-fetchable URLs
|
|
76
|
+
- **`client.extractUrlPreview(url)`**
|
|
77
|
+
- **`client.extractUrlContent(url, options)`**
|
|
78
|
+
- Options: `{ includeMediaUrls: true }`, defaults to false
|
|
79
|
+
- **`client.extractUrlLinks(url)`**
|
|
80
|
+
- **`client.extractUrlMetadata(url, options)`**
|
|
81
|
+
- Options: `{ includeResponseBody: true }`, defaults to false
|
|
82
|
+
|
|
83
|
+
### Error Types
|
|
84
|
+
When you wrap the functions above in a try/catch, here are the errors you may encounter:
|
|
85
|
+
|
|
86
|
+
- **"RobotsBlockedError: URL is blocked by robots.txt"**
|
|
87
|
+
- URL is explicitly blocked by the website's robots.txt will error like this.
|
|
88
|
+
- **"Network Error: 402 Payment Required"**
|
|
89
|
+
- Check your wallet -- likely you ran out of USDC to pay!
|
|
90
|
+
- **"Network Error: 502 Bad Gateway"**
|
|
91
|
+
- URLs that pass the robots.txt check but are blocked anyway via 403 or other tactics may error like this.
|
|
92
|
+
- **"503 Service Temporarily Unavailable"**
|
|
93
|
+
- Likely encountering Minifetch rate-limiting or upstream timeout errors on the target URL. You may try again but limit requests to 5-10 at a time. Bulk fetches coming soon.
|
|
94
|
+
- **"InvalidURLError: Invalid url ${url}"**
|
|
95
|
+
- The URL you passed in is malformed in some way, correct it and try again.
|
|
96
|
+
|
|
97
|
+
### Service Limitations
|
|
98
|
+
Minifetch only extracts publicly available metadata and content from pages accessible without authentication and javascript execution.
|
|
99
|
+
|
|
100
|
+
What Minifetch does NOT do:
|
|
101
|
+
|
|
102
|
+
- Ignore robots.txt directives
|
|
103
|
+
- Access authenticated or logged-in content
|
|
104
|
+
- Create accounts or log into user sessions
|
|
105
|
+
- Perform transactional actions (checkout, bidding, purchasing, form submissions)
|
|
106
|
+
- Bypass paywalls or access restricted content
|
|
107
|
+
|
|
108
|
+
What Minifetch does NOT do *currently* but may offer in the future as an add-on:
|
|
109
|
+
- Javascript execution
|
|
110
|
+
|
|
111
|
+
## x402 Best Practices
|
|
112
|
+
|
|
113
|
+
- LLMs & Agents should *never* have direct access to your private key. They *will* expose it!
|
|
114
|
+
- Only keep a small amount of USDC in the account whose private key you use.
|
|
115
|
+
- Keep that account separate from the rest of your onchain funds.
|
|
116
|
+
- Pass your private key into the Minifetch API Client via an [environment variable](https://developer.vonage.com/en/blog/how-to-use-environment-variables-in-javascript-with-dotenv).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
**License**
|
|
120
|
+
|
|
121
|
+
MIT / Copyright (c) 2026 Lauren Garcia
|
|
122
|
+
This package is an API client for Minifetch.com. The client code is open source, but use of the Minifetch API is subject to the [Minifetch.com Terms of Service](https://minifetch.com/terms-of-service).
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { initConfig } from "./init.js";
|
|
2
|
+
import { validateAndNormalizeUrl } from "./utils/validation.js";
|
|
3
|
+
import { handlePayment } from "./utils/payment.js";
|
|
4
|
+
import { InvalidUrlError, RobotsBlockedError, PaymentFailedError, ExtractionFailedError, NetworkError, } from "./types/errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Main Minifetch API client
|
|
7
|
+
* Provides methods to check URLs and extract metadata/links/preview/content
|
|
8
|
+
*/
|
|
9
|
+
export class MinifetchClient {
|
|
10
|
+
config;
|
|
11
|
+
baseUrl;
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param config
|
|
15
|
+
*/
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = initConfig(config);
|
|
18
|
+
this.baseUrl = this.config.apiBaseUrl;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if URL is allowed by robots.txt (free preflight check)
|
|
22
|
+
*
|
|
23
|
+
* @param url
|
|
24
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
25
|
+
* @throws {NetworkError} if request fails
|
|
26
|
+
*/
|
|
27
|
+
async preflightUrlCheck(url) {
|
|
28
|
+
try {
|
|
29
|
+
// Validate and normalize URL
|
|
30
|
+
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
31
|
+
// Build request URL (free endpoint, no payment)
|
|
32
|
+
const endpoint = `/api/v1/free/preflight/url-check?url=${encodeURIComponent(normalizedUrl)}`;
|
|
33
|
+
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
34
|
+
const response = await fetch(requestUrl);
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new NetworkError(`Preflight check failed: ${response.status} ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
const data = (await response.json());
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof InvalidUrlError || error instanceof NetworkError) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
throw new NetworkError(`Preflight check failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract URL metadata (paid, requires x402 payment)
|
|
50
|
+
*
|
|
51
|
+
* @param url
|
|
52
|
+
* @param options
|
|
53
|
+
* @param options.includeResponseBody
|
|
54
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
55
|
+
* @throws {PaymentFailedError} if payment fails
|
|
56
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
57
|
+
*/
|
|
58
|
+
async extractUrlMetadata(url, options) {
|
|
59
|
+
try {
|
|
60
|
+
// Validate and normalize URL
|
|
61
|
+
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
62
|
+
// Build request URL with optional params
|
|
63
|
+
const params = new URLSearchParams({ url: normalizedUrl });
|
|
64
|
+
if (options?.includeResponseBody) {
|
|
65
|
+
params.set("includeResponseBody", "true");
|
|
66
|
+
}
|
|
67
|
+
// Build request URL - convert params to string
|
|
68
|
+
const endpoint = `/api/v1/x402/extract/url-metadata?${params.toString()}`;
|
|
69
|
+
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
70
|
+
// Make request with x402 payment handling
|
|
71
|
+
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
72
|
+
// Check if extraction succeeded
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new ExtractionFailedError(normalizedUrl, `Metadata extraction failed: ${response.status} ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
const data = (await response.json());
|
|
77
|
+
return {
|
|
78
|
+
success: data.success,
|
|
79
|
+
results: data.results,
|
|
80
|
+
payment,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof InvalidUrlError ||
|
|
85
|
+
error instanceof ExtractionFailedError ||
|
|
86
|
+
error instanceof PaymentFailedError ||
|
|
87
|
+
error instanceof NetworkError) {
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
throw new ExtractionFailedError(url, `Metadata extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Extract URL links (paid, requires x402 payment)
|
|
95
|
+
*
|
|
96
|
+
* @param url
|
|
97
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
98
|
+
* @throws {PaymentFailedError} if payment fails
|
|
99
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
100
|
+
*/
|
|
101
|
+
async extractUrlLinks(url) {
|
|
102
|
+
try {
|
|
103
|
+
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
104
|
+
const endpoint = `/api/v1/x402/extract/url-links?url=${encodeURIComponent(normalizedUrl)}`;
|
|
105
|
+
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
106
|
+
// Make request with x402 payment handling
|
|
107
|
+
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new ExtractionFailedError(normalizedUrl, `Links extraction failed: ${response.status} ${response.statusText}`);
|
|
110
|
+
}
|
|
111
|
+
const data = (await response.json());
|
|
112
|
+
return {
|
|
113
|
+
success: data.success,
|
|
114
|
+
results: data.results,
|
|
115
|
+
payment,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error instanceof InvalidUrlError ||
|
|
120
|
+
error instanceof ExtractionFailedError ||
|
|
121
|
+
error instanceof PaymentFailedError ||
|
|
122
|
+
error instanceof NetworkError) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
throw new ExtractionFailedError(url, `Links extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Extract URL preview (paid, requires x402 payment)
|
|
130
|
+
*
|
|
131
|
+
* @param url
|
|
132
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
133
|
+
* @throws {PaymentFailedError} if payment fails
|
|
134
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
135
|
+
*/
|
|
136
|
+
async extractUrlPreview(url) {
|
|
137
|
+
try {
|
|
138
|
+
// Validate and normalize URL
|
|
139
|
+
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
140
|
+
// Build request URL
|
|
141
|
+
const endpoint = `/api/v1/x402/extract/url-preview?url=${encodeURIComponent(normalizedUrl)}`;
|
|
142
|
+
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
143
|
+
// Make request with x402 payment handling
|
|
144
|
+
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
145
|
+
// Check if extraction succeeded
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new ExtractionFailedError(normalizedUrl, `Preview extraction failed: ${response.status} ${response.statusText}`);
|
|
148
|
+
}
|
|
149
|
+
const data = (await response.json());
|
|
150
|
+
return {
|
|
151
|
+
success: data.success,
|
|
152
|
+
results: data.results,
|
|
153
|
+
payment,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error instanceof InvalidUrlError ||
|
|
158
|
+
error instanceof ExtractionFailedError ||
|
|
159
|
+
error instanceof PaymentFailedError ||
|
|
160
|
+
error instanceof NetworkError) {
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
throw new ExtractionFailedError(url, `Preview extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Extract URL content as markdown (paid, requires x402 payment)
|
|
168
|
+
*
|
|
169
|
+
* @param url
|
|
170
|
+
* @param options
|
|
171
|
+
* @param options.includeMediaUrls
|
|
172
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
173
|
+
* @throws {PaymentFailedError} if payment fails
|
|
174
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
175
|
+
*/
|
|
176
|
+
async extractUrlContent(url, options) {
|
|
177
|
+
try {
|
|
178
|
+
// Validate and normalize URL
|
|
179
|
+
const normalizedUrl = validateAndNormalizeUrl(url);
|
|
180
|
+
// Build request URL with optional params
|
|
181
|
+
const params = new URLSearchParams({ url: normalizedUrl });
|
|
182
|
+
if (options?.includeMediaUrls) {
|
|
183
|
+
params.set("includeMediaUrls", "true");
|
|
184
|
+
}
|
|
185
|
+
const endpoint = `/api/v1/x402/extract/url-content?${params.toString()}`;
|
|
186
|
+
const requestUrl = `${this.baseUrl}${endpoint}`;
|
|
187
|
+
// Make request with x402 payment handling
|
|
188
|
+
const { response, payment } = await handlePayment(requestUrl, this.config);
|
|
189
|
+
// Check if extraction succeeded
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
throw new ExtractionFailedError(normalizedUrl, `Content extraction failed: ${response.status} ${response.statusText}`);
|
|
192
|
+
}
|
|
193
|
+
const data = (await response.json());
|
|
194
|
+
return {
|
|
195
|
+
success: data.success,
|
|
196
|
+
results: data.results,
|
|
197
|
+
payment,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error instanceof InvalidUrlError ||
|
|
202
|
+
error instanceof ExtractionFailedError ||
|
|
203
|
+
error instanceof PaymentFailedError ||
|
|
204
|
+
error instanceof NetworkError) {
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
throw new ExtractionFailedError(url, `Content extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check URL and extract metadata in one call
|
|
212
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
213
|
+
*
|
|
214
|
+
* @param url
|
|
215
|
+
* @param options
|
|
216
|
+
* @param options.includeResponseBody
|
|
217
|
+
*/
|
|
218
|
+
async checkAndExtractUrlMetadata(url, options) {
|
|
219
|
+
const checkResponse = await this.preflightUrlCheck(url);
|
|
220
|
+
if (!checkResponse.results[0]?.data?.allowed) {
|
|
221
|
+
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
222
|
+
}
|
|
223
|
+
return this.extractUrlMetadata(url, options);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Check URL and extract links in one call
|
|
227
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
228
|
+
*
|
|
229
|
+
* @param url
|
|
230
|
+
*/
|
|
231
|
+
async checkAndExtractUrlLinks(url) {
|
|
232
|
+
const checkResponse = await this.preflightUrlCheck(url);
|
|
233
|
+
if (!checkResponse.results[0]?.data?.allowed) {
|
|
234
|
+
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
235
|
+
}
|
|
236
|
+
return this.extractUrlLinks(url);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check URL and extract preview in one call
|
|
240
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
241
|
+
*
|
|
242
|
+
* @param url
|
|
243
|
+
*/
|
|
244
|
+
async checkAndExtractUrlPreview(url) {
|
|
245
|
+
const checkResponse = await this.preflightUrlCheck(url);
|
|
246
|
+
if (!checkResponse.results[0]?.data?.allowed) {
|
|
247
|
+
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
248
|
+
}
|
|
249
|
+
return this.extractUrlPreview(url);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Check URL and extract content in one call
|
|
253
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
254
|
+
*
|
|
255
|
+
* @param url
|
|
256
|
+
* @param options
|
|
257
|
+
* @param options.includeMediaUrls
|
|
258
|
+
*/
|
|
259
|
+
async checkAndExtractUrlContent(url, options) {
|
|
260
|
+
const checkResponse = await this.preflightUrlCheck(url);
|
|
261
|
+
if (!checkResponse.results[0]?.data?.allowed) {
|
|
262
|
+
throw new RobotsBlockedError(url, checkResponse.results[0]?.data?.message || "URL is blocked by robots.txt");
|
|
263
|
+
}
|
|
264
|
+
return this.extractUrlContent(url, options);
|
|
265
|
+
}
|
|
266
|
+
}
|
package/dist/esm/init.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { VALID_NETWORKS } from "./types/config.js";
|
|
2
|
+
import { ConfigurationError } from "./types/errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default configuration values
|
|
5
|
+
* Exported for testing purposes
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULTS = {
|
|
8
|
+
network: "base",
|
|
9
|
+
apiBaseUrls: {
|
|
10
|
+
"base-sepolia": "http://localhost:4021",
|
|
11
|
+
"solana-devnet": "http://localhost:4021",
|
|
12
|
+
base: "https://minifetch.com",
|
|
13
|
+
solana: "https://minifetch.com",
|
|
14
|
+
},
|
|
15
|
+
explorerUrls: {
|
|
16
|
+
"base-sepolia": "https://sepolia.basescan.org/tx",
|
|
17
|
+
"solana-devnet": "https://explorer.solana.com/tx?cluster=devnet",
|
|
18
|
+
base: "https://basescan.org/tx",
|
|
19
|
+
solana: "https://explorer.solana.com/tx",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Validate & initialize client configuration
|
|
24
|
+
*
|
|
25
|
+
* @param config
|
|
26
|
+
*/
|
|
27
|
+
export function initConfig(config) {
|
|
28
|
+
// Validate private key is provided
|
|
29
|
+
if (!config.privateKey || config.privateKey.trim() === "") {
|
|
30
|
+
throw new ConfigurationError("Private key is required");
|
|
31
|
+
}
|
|
32
|
+
// Validate network
|
|
33
|
+
const network = config.network || DEFAULTS.network;
|
|
34
|
+
if (!VALID_NETWORKS.includes(network)) {
|
|
35
|
+
throw new ConfigurationError(`Invalid network: "${network}". Must be one of: ${VALID_NETWORKS.join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
// Validate private key format based on network
|
|
38
|
+
validatePrivateKey(config.privateKey, network);
|
|
39
|
+
// Build initalized config
|
|
40
|
+
const apiBaseUrl = DEFAULTS.apiBaseUrls[network];
|
|
41
|
+
const explorerUrl = DEFAULTS.explorerUrls[network];
|
|
42
|
+
// console.log(`config.ts: network: ${network} apiBaseUrl: ${apiBaseUrl}`)
|
|
43
|
+
return {
|
|
44
|
+
network,
|
|
45
|
+
privateKey: config.privateKey,
|
|
46
|
+
apiBaseUrl,
|
|
47
|
+
explorerUrl,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate private key format for the given network
|
|
52
|
+
*
|
|
53
|
+
* @param privateKey
|
|
54
|
+
* @param network
|
|
55
|
+
*/
|
|
56
|
+
function validatePrivateKey(privateKey, network) {
|
|
57
|
+
if (network === "solana" || network === "solana-devnet") {
|
|
58
|
+
// Solana: base58 encoded, typically 88 characters
|
|
59
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{87,88}$/.test(privateKey)) {
|
|
60
|
+
throw new ConfigurationError("Invalid Solana private key format (expected base58 string)");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// EVM: hex string, 64 characters (with or without 0x prefix)
|
|
65
|
+
const cleanKey = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
|
|
66
|
+
if (!/^[0-9a-fA-F]{64}$/.test(cleanKey)) {
|
|
67
|
+
throw new ConfigurationError("Invalid EVM private key format (expected hex string that starts with 0x)");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VALID_NETWORKS = ["base", "base-sepolia", "solana", "solana-devnet"];
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Minifetch errors
|
|
3
|
+
*/
|
|
4
|
+
export class MinifetchError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param message
|
|
8
|
+
*/
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "MinifetchError";
|
|
12
|
+
Object.setPrototypeOf(this, MinifetchError.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when URL validation fails
|
|
17
|
+
*/
|
|
18
|
+
export class InvalidUrlError extends MinifetchError {
|
|
19
|
+
url;
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param url
|
|
23
|
+
* @param message
|
|
24
|
+
*/
|
|
25
|
+
constructor(url, message) {
|
|
26
|
+
super(message || `Invalid url: ${url}`);
|
|
27
|
+
this.name = "InvalidUrlError";
|
|
28
|
+
this.url = url;
|
|
29
|
+
Object.setPrototypeOf(this, InvalidUrlError.prototype);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Thrown when robots.txt blocks the request
|
|
34
|
+
*/
|
|
35
|
+
export class RobotsBlockedError extends MinifetchError {
|
|
36
|
+
url;
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param url
|
|
40
|
+
* @param message
|
|
41
|
+
*/
|
|
42
|
+
constructor(url, message) {
|
|
43
|
+
super(message || `URL is blocked by robots.txt`);
|
|
44
|
+
this.name = "RobotsBlockedError";
|
|
45
|
+
this.url = url;
|
|
46
|
+
Object.setPrototypeOf(this, RobotsBlockedError.prototype);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when payment fails
|
|
51
|
+
*/
|
|
52
|
+
export class PaymentFailedError extends MinifetchError {
|
|
53
|
+
network;
|
|
54
|
+
originalError;
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @param message
|
|
58
|
+
* @param network
|
|
59
|
+
* @param originalError
|
|
60
|
+
*/
|
|
61
|
+
constructor(message, network, originalError) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "PaymentFailedError";
|
|
64
|
+
this.network = network;
|
|
65
|
+
this.originalError = originalError;
|
|
66
|
+
Object.setPrototypeOf(this, PaymentFailedError.prototype);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown when extraction/fetch fails
|
|
71
|
+
*/
|
|
72
|
+
export class ExtractionFailedError extends MinifetchError {
|
|
73
|
+
url;
|
|
74
|
+
statusCode;
|
|
75
|
+
originalError;
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* @param url
|
|
79
|
+
* @param message
|
|
80
|
+
* @param statusCode
|
|
81
|
+
* @param originalError
|
|
82
|
+
*/
|
|
83
|
+
constructor(url, message, statusCode, originalError) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = "ExtractionFailedError";
|
|
86
|
+
this.url = url;
|
|
87
|
+
this.statusCode = statusCode;
|
|
88
|
+
this.originalError = originalError;
|
|
89
|
+
Object.setPrototypeOf(this, ExtractionFailedError.prototype);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Thrown when configuration is invalid
|
|
94
|
+
*/
|
|
95
|
+
export class ConfigurationError extends MinifetchError {
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param message
|
|
99
|
+
*/
|
|
100
|
+
constructor(message) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "ConfigurationError";
|
|
103
|
+
Object.setPrototypeOf(this, ConfigurationError.prototype);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Thrown when network/API communication fails
|
|
108
|
+
*/
|
|
109
|
+
export class NetworkError extends MinifetchError {
|
|
110
|
+
originalError;
|
|
111
|
+
/**
|
|
112
|
+
*
|
|
113
|
+
* @param message
|
|
114
|
+
* @param originalError
|
|
115
|
+
*/
|
|
116
|
+
constructor(message, originalError) {
|
|
117
|
+
super(message);
|
|
118
|
+
this.name = "NetworkError";
|
|
119
|
+
this.originalError = originalError;
|
|
120
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|