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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { x402Client, wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch";
|
|
2
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
3
|
+
import { registerExactSvmScheme } from "@x402/svm/exact/client";
|
|
4
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
5
|
+
import { createKeyPairSignerFromBytes } from "@solana/kit";
|
|
6
|
+
import bs58 from "bs58";
|
|
7
|
+
import { PaymentFailedError, NetworkError } from "../types/errors.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle x402 payment flow using Coinbase x402 client pattern
|
|
10
|
+
* 1. Initialize x402Client and register payment scheme
|
|
11
|
+
* 2. Wrap fetch with payment capabilities
|
|
12
|
+
* 3. Make request (client handles 402 detection and payment)
|
|
13
|
+
* 4. Extract payment receipt from response headers
|
|
14
|
+
*
|
|
15
|
+
* @param url
|
|
16
|
+
* @param config
|
|
17
|
+
*/
|
|
18
|
+
export async function handlePayment(url, config) {
|
|
19
|
+
if (!config.privateKey) {
|
|
20
|
+
throw new PaymentFailedError("Private key required for payments");
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
// Create x402 client and signer based on network type
|
|
24
|
+
const _x402Client = new x402Client();
|
|
25
|
+
let payer;
|
|
26
|
+
const isEvm = config.network.startsWith("base");
|
|
27
|
+
const isSolana = config.network.startsWith("solana");
|
|
28
|
+
if (isEvm) {
|
|
29
|
+
// Initialize EVM signer and register scheme
|
|
30
|
+
const signer = privateKeyToAccount(config.privateKey);
|
|
31
|
+
const evmSigner = signer; // Type assertion for EVM
|
|
32
|
+
registerExactEvmScheme(_x402Client, { signer: evmSigner });
|
|
33
|
+
payer = signer.address;
|
|
34
|
+
}
|
|
35
|
+
else if (isSolana) {
|
|
36
|
+
// Initialize Solana signer and register scheme
|
|
37
|
+
const privateKeyBytes = bs58.decode(config.privateKey);
|
|
38
|
+
const signer = await createKeyPairSignerFromBytes(privateKeyBytes);
|
|
39
|
+
const svmSigner = signer; // Type assertion for Solana
|
|
40
|
+
registerExactSvmScheme(_x402Client, { signer: svmSigner });
|
|
41
|
+
payer = signer.address;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
throw new PaymentFailedError(`Unsupported network: ${config.network}`);
|
|
45
|
+
}
|
|
46
|
+
// Wrap fetch with payment capabilities
|
|
47
|
+
const fetchWithPayment = wrapFetchWithPayment(fetch, _x402Client);
|
|
48
|
+
// Make request with payment handling
|
|
49
|
+
// console.log("Attempting to fetch w payment:", url);
|
|
50
|
+
const response = await fetchWithPayment(url, {
|
|
51
|
+
method: "GET",
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new NetworkError(`Request failed: ${response.status} ${response.statusText}`);
|
|
55
|
+
}
|
|
56
|
+
// Extract payment receipt from response headers
|
|
57
|
+
const httpClient = new x402HTTPClient(_x402Client);
|
|
58
|
+
const paymentResponse = httpClient.getPaymentSettleResponse(name => response.headers.get(name));
|
|
59
|
+
// DEBUG:
|
|
60
|
+
// console.log("Payment response:");
|
|
61
|
+
// console.log(paymentResponse);
|
|
62
|
+
// Build payment info for user
|
|
63
|
+
const payment = {
|
|
64
|
+
success: true,
|
|
65
|
+
payer,
|
|
66
|
+
network: config.network,
|
|
67
|
+
txHash: paymentResponse.transaction || "",
|
|
68
|
+
explorerLink: paymentResponse.transaction
|
|
69
|
+
? getExplorerLink(config, paymentResponse.transaction)
|
|
70
|
+
: "",
|
|
71
|
+
};
|
|
72
|
+
return { response, payment };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof PaymentFailedError || error instanceof NetworkError) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
throw new PaymentFailedError(`Payment failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Helper fn to build explorer links
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param config
|
|
85
|
+
* @param txHash
|
|
86
|
+
*/
|
|
87
|
+
function getExplorerLink(config, txHash) {
|
|
88
|
+
if (config.network === "solana-devnet") {
|
|
89
|
+
const strArray = config.explorerUrl.split("?");
|
|
90
|
+
return `${strArray[0]}/${txHash}?${strArray[1]}`;
|
|
91
|
+
}
|
|
92
|
+
else if (txHash) {
|
|
93
|
+
return `${config.explorerUrl}/${txHash}`;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { InvalidUrlError } from "../types/errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Maximum allowed URL length
|
|
4
|
+
*/
|
|
5
|
+
const MAX_URL_LENGTH = 2048;
|
|
6
|
+
/**
|
|
7
|
+
* Allowed URL protocols
|
|
8
|
+
*/
|
|
9
|
+
const ALLOWED_PROTOCOLS = ["http:", "https:"];
|
|
10
|
+
/**
|
|
11
|
+
* Unsupported file extensions
|
|
12
|
+
*/
|
|
13
|
+
const UNSUPPORTED_EXTENSIONS = [
|
|
14
|
+
".pdf",
|
|
15
|
+
".txt",
|
|
16
|
+
".md",
|
|
17
|
+
".doc",
|
|
18
|
+
".docx",
|
|
19
|
+
".xls",
|
|
20
|
+
".xlsx",
|
|
21
|
+
".zip",
|
|
22
|
+
".tar",
|
|
23
|
+
".gz",
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Validate and normalize a URL string
|
|
27
|
+
* Auto-normalizes by adding https:// if no protocol is present
|
|
28
|
+
*
|
|
29
|
+
* @param url
|
|
30
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
31
|
+
*/
|
|
32
|
+
export function validateAndNormalizeUrl(url) {
|
|
33
|
+
// Check if URL is provided
|
|
34
|
+
if (!url || typeof url !== "string") {
|
|
35
|
+
throw new InvalidUrlError(url, "URL must be a non-empty string");
|
|
36
|
+
}
|
|
37
|
+
// Normalize: add https:// if no protocol
|
|
38
|
+
let normalized = url.trim();
|
|
39
|
+
if (!normalized.match(/^https?:\/\//i)) {
|
|
40
|
+
normalized = `https://${normalized}`;
|
|
41
|
+
}
|
|
42
|
+
// Check URL length
|
|
43
|
+
if (normalized.length > MAX_URL_LENGTH) {
|
|
44
|
+
throw new InvalidUrlError(normalized, `URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
|
|
45
|
+
}
|
|
46
|
+
// Try to parse URL
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = new URL(normalized);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw new InvalidUrlError(normalized, "Invalid URL format");
|
|
53
|
+
}
|
|
54
|
+
// Validate protocol
|
|
55
|
+
if (!ALLOWED_PROTOCOLS.includes(parsed.protocol)) {
|
|
56
|
+
throw new InvalidUrlError(normalized, `Protocol must be http: or https:, got: ${parsed.protocol}`);
|
|
57
|
+
}
|
|
58
|
+
// Validate hostname exists
|
|
59
|
+
if (!parsed.hostname) {
|
|
60
|
+
throw new InvalidUrlError(normalized, "URL must have a valid hostname");
|
|
61
|
+
}
|
|
62
|
+
// Validate hostname has at least one dot (e.g., example.com)
|
|
63
|
+
if (!parsed.hostname.includes(".")) {
|
|
64
|
+
throw new InvalidUrlError(normalized, "URL must have a valid domain with a TLD");
|
|
65
|
+
}
|
|
66
|
+
// Check for unsupported file extensions
|
|
67
|
+
const pathname = parsed.pathname.toLowerCase();
|
|
68
|
+
for (const ext of UNSUPPORTED_EXTENSIONS) {
|
|
69
|
+
if (pathname.endsWith(ext)) {
|
|
70
|
+
throw new InvalidUrlError(normalized, `Unsupported file format: ${ext}. Only HTML pages are supported.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Basic check for localhost/private IPs
|
|
74
|
+
// API server will do deeper SSRF validation
|
|
75
|
+
if (isPrivateOrLocalhost(parsed.hostname)) {
|
|
76
|
+
throw new InvalidUrlError(normalized, "Cannot fetch from localhost or private IP addresses");
|
|
77
|
+
}
|
|
78
|
+
return normalized;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if hostname is localhost or private IP
|
|
82
|
+
*
|
|
83
|
+
* @param hostname
|
|
84
|
+
*/
|
|
85
|
+
function isPrivateOrLocalhost(hostname) {
|
|
86
|
+
// Localhost
|
|
87
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// Private IP ranges (basic check)
|
|
91
|
+
const privateIpPatterns = [
|
|
92
|
+
/^10\./, // 10.0.0.0/8
|
|
93
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
|
|
94
|
+
/^192\.168\./, // 192.168.0.0/16
|
|
95
|
+
/^169\.254\./, // 169.254.0.0/16 (link-local)
|
|
96
|
+
];
|
|
97
|
+
return privateIpPatterns.some(pattern => pattern.test(hostname));
|
|
98
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ClientConfig } from "./types/config.js";
|
|
2
|
+
import type { PreflightCheckResponse, PaidEndpointResponse } from "./types/responses.js";
|
|
3
|
+
/**
|
|
4
|
+
* Main Minifetch API client
|
|
5
|
+
* Provides methods to check URLs and extract metadata/links/preview/content
|
|
6
|
+
*/
|
|
7
|
+
export declare class MinifetchClient {
|
|
8
|
+
private config;
|
|
9
|
+
private baseUrl;
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param config
|
|
13
|
+
*/
|
|
14
|
+
constructor(config: ClientConfig);
|
|
15
|
+
/**
|
|
16
|
+
* Check if URL is allowed by robots.txt (free preflight check)
|
|
17
|
+
*
|
|
18
|
+
* @param url
|
|
19
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
20
|
+
* @throws {NetworkError} if request fails
|
|
21
|
+
*/
|
|
22
|
+
preflightUrlCheck(url: string): Promise<PreflightCheckResponse>;
|
|
23
|
+
/**
|
|
24
|
+
* Extract URL metadata (paid, requires x402 payment)
|
|
25
|
+
*
|
|
26
|
+
* @param url
|
|
27
|
+
* @param options
|
|
28
|
+
* @param options.includeResponseBody
|
|
29
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
30
|
+
* @throws {PaymentFailedError} if payment fails
|
|
31
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
32
|
+
*/
|
|
33
|
+
extractUrlMetadata(url: string, options?: {
|
|
34
|
+
includeResponseBody?: boolean;
|
|
35
|
+
}): Promise<PaidEndpointResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Extract URL links (paid, requires x402 payment)
|
|
38
|
+
*
|
|
39
|
+
* @param url
|
|
40
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
41
|
+
* @throws {PaymentFailedError} if payment fails
|
|
42
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
43
|
+
*/
|
|
44
|
+
extractUrlLinks(url: string): Promise<PaidEndpointResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* Extract URL preview (paid, requires x402 payment)
|
|
47
|
+
*
|
|
48
|
+
* @param url
|
|
49
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
50
|
+
* @throws {PaymentFailedError} if payment fails
|
|
51
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
52
|
+
*/
|
|
53
|
+
extractUrlPreview(url: string): Promise<PaidEndpointResponse>;
|
|
54
|
+
/**
|
|
55
|
+
* Extract URL content as markdown (paid, requires x402 payment)
|
|
56
|
+
*
|
|
57
|
+
* @param url
|
|
58
|
+
* @param options
|
|
59
|
+
* @param options.includeMediaUrls
|
|
60
|
+
* @throws {InvalidUrlError} if URL is invalid
|
|
61
|
+
* @throws {PaymentFailedError} if payment fails
|
|
62
|
+
* @throws {ExtractionFailedError} if extraction fails
|
|
63
|
+
*/
|
|
64
|
+
extractUrlContent(url: string, options?: {
|
|
65
|
+
includeMediaUrls?: boolean;
|
|
66
|
+
}): Promise<PaidEndpointResponse>;
|
|
67
|
+
/**
|
|
68
|
+
* Check URL and extract metadata in one call
|
|
69
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
70
|
+
*
|
|
71
|
+
* @param url
|
|
72
|
+
* @param options
|
|
73
|
+
* @param options.includeResponseBody
|
|
74
|
+
*/
|
|
75
|
+
checkAndExtractUrlMetadata(url: string, options?: {
|
|
76
|
+
includeResponseBody?: boolean;
|
|
77
|
+
}): Promise<PaidEndpointResponse>;
|
|
78
|
+
/**
|
|
79
|
+
* Check URL and extract links in one call
|
|
80
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
81
|
+
*
|
|
82
|
+
* @param url
|
|
83
|
+
*/
|
|
84
|
+
checkAndExtractUrlLinks(url: string): Promise<PaidEndpointResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* Check URL and extract preview in one call
|
|
87
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
88
|
+
*
|
|
89
|
+
* @param url
|
|
90
|
+
*/
|
|
91
|
+
checkAndExtractUrlPreview(url: string): Promise<PaidEndpointResponse>;
|
|
92
|
+
/**
|
|
93
|
+
* Check URL and extract content in one call
|
|
94
|
+
* Throws RobotsBlockedError if robots.txt blocks the URL
|
|
95
|
+
*
|
|
96
|
+
* @param url
|
|
97
|
+
* @param options
|
|
98
|
+
* @param options.includeMediaUrls
|
|
99
|
+
*/
|
|
100
|
+
checkAndExtractUrlContent(url: string, options?: {
|
|
101
|
+
includeMediaUrls?: boolean;
|
|
102
|
+
}): Promise<PaidEndpointResponse>;
|
|
103
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ClientConfig, InitializedConfig, Network } from "./types/config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Default configuration values
|
|
4
|
+
* Exported for testing purposes
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULTS: {
|
|
7
|
+
readonly network: Network;
|
|
8
|
+
readonly apiBaseUrls: {
|
|
9
|
+
readonly "base-sepolia": "http://localhost:4021";
|
|
10
|
+
readonly "solana-devnet": "http://localhost:4021";
|
|
11
|
+
readonly base: "https://minifetch.com";
|
|
12
|
+
readonly solana: "https://minifetch.com";
|
|
13
|
+
};
|
|
14
|
+
readonly explorerUrls: {
|
|
15
|
+
readonly "base-sepolia": "https://sepolia.basescan.org/tx";
|
|
16
|
+
readonly "solana-devnet": "https://explorer.solana.com/tx?cluster=devnet";
|
|
17
|
+
readonly base: "https://basescan.org/tx";
|
|
18
|
+
readonly solana: "https://explorer.solana.com/tx";
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Validate & initialize client configuration
|
|
23
|
+
*
|
|
24
|
+
* @param config
|
|
25
|
+
*/
|
|
26
|
+
export declare function initConfig(config: ClientConfig): InitializedConfig;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const VALID_NETWORKS: readonly ["base", "base-sepolia", "solana", "solana-devnet"];
|
|
2
|
+
export type Network = (typeof VALID_NETWORKS)[number];
|
|
3
|
+
export interface ClientConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Blockchain network to use for payments
|
|
6
|
+
*
|
|
7
|
+
* @default 'base'
|
|
8
|
+
*/
|
|
9
|
+
network: Network;
|
|
10
|
+
/**
|
|
11
|
+
* Private key for signing transactions -
|
|
12
|
+
* hex string for EVM, base58 for Solana
|
|
13
|
+
*/
|
|
14
|
+
privateKey: string;
|
|
15
|
+
}
|
|
16
|
+
export interface InitializedConfig {
|
|
17
|
+
network: Network;
|
|
18
|
+
privateKey: string;
|
|
19
|
+
apiBaseUrl: string;
|
|
20
|
+
explorerUrl: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Minifetch errors
|
|
3
|
+
*/
|
|
4
|
+
export declare class MinifetchError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param message
|
|
8
|
+
*/
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Thrown when URL validation fails
|
|
13
|
+
*/
|
|
14
|
+
export declare class InvalidUrlError extends MinifetchError {
|
|
15
|
+
readonly url: string;
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param url
|
|
19
|
+
* @param message
|
|
20
|
+
*/
|
|
21
|
+
constructor(url: string, message?: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when robots.txt blocks the request
|
|
25
|
+
*/
|
|
26
|
+
export declare class RobotsBlockedError extends MinifetchError {
|
|
27
|
+
readonly url: string;
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @param url
|
|
31
|
+
* @param message
|
|
32
|
+
*/
|
|
33
|
+
constructor(url: string, message?: string);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Thrown when payment fails
|
|
37
|
+
*/
|
|
38
|
+
export declare class PaymentFailedError extends MinifetchError {
|
|
39
|
+
readonly network?: string;
|
|
40
|
+
readonly originalError?: Error;
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param message
|
|
44
|
+
* @param network
|
|
45
|
+
* @param originalError
|
|
46
|
+
*/
|
|
47
|
+
constructor(message: string, network?: string, originalError?: Error);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when extraction/fetch fails
|
|
51
|
+
*/
|
|
52
|
+
export declare class ExtractionFailedError extends MinifetchError {
|
|
53
|
+
readonly url: string;
|
|
54
|
+
readonly statusCode?: number;
|
|
55
|
+
readonly originalError?: Error;
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param url
|
|
59
|
+
* @param message
|
|
60
|
+
* @param statusCode
|
|
61
|
+
* @param originalError
|
|
62
|
+
*/
|
|
63
|
+
constructor(url: string, message: string, statusCode?: number, originalError?: Error);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Thrown when configuration is invalid
|
|
67
|
+
*/
|
|
68
|
+
export declare class ConfigurationError extends MinifetchError {
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @param message
|
|
72
|
+
*/
|
|
73
|
+
constructor(message: string);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Thrown when network/API communication fails
|
|
77
|
+
*/
|
|
78
|
+
export declare class NetworkError extends MinifetchError {
|
|
79
|
+
readonly originalError?: Error;
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* @param message
|
|
83
|
+
* @param originalError
|
|
84
|
+
*/
|
|
85
|
+
constructor(message: string, originalError?: Error);
|
|
86
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result from preflight URL check (free endpoint)
|
|
3
|
+
* Note: This is the ONLY endpoint that doesn't require payment
|
|
4
|
+
*/
|
|
5
|
+
export interface PreflightCheckResponse {
|
|
6
|
+
success: boolean;
|
|
7
|
+
results: Array<{
|
|
8
|
+
data: {
|
|
9
|
+
url: string;
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
message?: string;
|
|
12
|
+
crawlDelay?: number;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
};
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Response structure for all paid API responses
|
|
19
|
+
*/
|
|
20
|
+
export interface PaidEndpointResponse {
|
|
21
|
+
/** Minifetch API success (200, ok) **/
|
|
22
|
+
success: boolean;
|
|
23
|
+
/** Data returned from the Minifetch.com API **/
|
|
24
|
+
results: Array<{
|
|
25
|
+
data: {
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
};
|
|
28
|
+
error?: Record<string, any>;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Payment information
|
|
32
|
+
* Only present for paid endpoints when request was successful
|
|
33
|
+
* */
|
|
34
|
+
payment?: PaymentInfo;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Payment information included with successful paid API responses
|
|
38
|
+
*/
|
|
39
|
+
export interface PaymentInfo {
|
|
40
|
+
/** Whether the payment was successful **/
|
|
41
|
+
success: boolean;
|
|
42
|
+
/** Account that paid for tx **/
|
|
43
|
+
payer: string;
|
|
44
|
+
/** Network the payment was made on **/
|
|
45
|
+
network: "base" | "base-sepolia" | "solana" | "solana-devnet";
|
|
46
|
+
/** Transaction hash **/
|
|
47
|
+
txHash: string;
|
|
48
|
+
/** Link to view transaction on block explorer **/
|
|
49
|
+
explorerLink: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InitializedConfig } from "../types/config.js";
|
|
2
|
+
import type { PaymentInfo } from "../types/responses.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handle x402 payment flow using Coinbase x402 client pattern
|
|
5
|
+
* 1. Initialize x402Client and register payment scheme
|
|
6
|
+
* 2. Wrap fetch with payment capabilities
|
|
7
|
+
* 3. Make request (client handles 402 detection and payment)
|
|
8
|
+
* 4. Extract payment receipt from response headers
|
|
9
|
+
*
|
|
10
|
+
* @param url
|
|
11
|
+
* @param config
|
|
12
|
+
*/
|
|
13
|
+
export declare function handlePayment(url: string, config: InitializedConfig): Promise<{
|
|
14
|
+
response: Response;
|
|
15
|
+
payment?: PaymentInfo;
|
|
16
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,38 +1,87 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minifetch-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Minifetch.com API Client. Fetch & extract data from web pages. For AI Agents & SEO research.",
|
|
4
5
|
"type": "module",
|
|
6
|
+
"main": "./dist/esm/index.js",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types/index.d.ts",
|
|
11
|
+
"import": "./dist/esm/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"seo",
|
|
21
|
+
"metadata",
|
|
22
|
+
"meta-tags",
|
|
23
|
+
"web-pages",
|
|
24
|
+
"web-scraping",
|
|
25
|
+
"scraper",
|
|
26
|
+
"html-parser",
|
|
27
|
+
"ai",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"llm",
|
|
30
|
+
"x402",
|
|
31
|
+
"micropayments",
|
|
32
|
+
"usdc"
|
|
33
|
+
],
|
|
5
34
|
"bugs": {
|
|
6
35
|
"url": "https://github.com/Niche-Networks/minifetch-api/issues"
|
|
7
36
|
},
|
|
8
|
-
"homepage": "https://minifetch.com
|
|
37
|
+
"homepage": "https://minifetch.com",
|
|
9
38
|
"repository": {
|
|
10
39
|
"type": "git",
|
|
11
40
|
"url": "git@github.com:Niche-Networks/minifetch-api.git"
|
|
12
41
|
},
|
|
13
42
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
43
|
+
"dist:clean": "rm -rf dist",
|
|
44
|
+
"build": "pnpm run dist:clean && pnpm run build:esm",
|
|
45
|
+
"build:esm": "tsc -p tsconfig.json",
|
|
16
46
|
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
|
|
17
47
|
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
|
|
18
48
|
"lint": "eslint . --ext .ts --fix",
|
|
19
|
-
"lint:check": "eslint . --ext .ts"
|
|
49
|
+
"lint:check": "eslint . --ext .ts",
|
|
50
|
+
"test": "SKIP_DEBUG_TESTS=true vitest",
|
|
51
|
+
"test:run": "SKIP_DEBUG_TESTS=true vitest run",
|
|
52
|
+
"test:file": "vitest run",
|
|
53
|
+
"test:debug": "vitest run test/debug.test.ts",
|
|
54
|
+
"test:coverage": "vitest run --coverage",
|
|
55
|
+
"example:ts": "cd example-typescript && pnpm install && pnpm start",
|
|
56
|
+
"example:js": "cd example-javascript && pnpm install && pnpm start",
|
|
57
|
+
"prepublishOnly": "pnpm run build"
|
|
20
58
|
},
|
|
21
59
|
"dependencies": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"x402
|
|
60
|
+
"@solana/kit": "^5.4.0",
|
|
61
|
+
"@x402/evm": "^2.2.0",
|
|
62
|
+
"@x402/fetch": "^2.2.0",
|
|
63
|
+
"@x402/svm": "^2.2.0",
|
|
64
|
+
"bs58": "^6.0.0",
|
|
65
|
+
"viem": "^2.23.1"
|
|
25
66
|
},
|
|
26
67
|
"devDependencies": {
|
|
27
68
|
"@eslint/js": "^9.24.0",
|
|
28
|
-
"
|
|
29
|
-
"eslint-plugin-jsdoc": "^50.6.9",
|
|
30
|
-
"eslint-plugin-prettier": "^5.2.6",
|
|
69
|
+
"@types/node": "^25.2.0",
|
|
31
70
|
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
|
32
71
|
"@typescript-eslint/parser": "^8.29.1",
|
|
72
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
73
|
+
"dotenv": "^16.4.7",
|
|
74
|
+
"eslint": "^9.24.0",
|
|
33
75
|
"eslint-plugin-import": "^2.31.0",
|
|
76
|
+
"eslint-plugin-jsdoc": "^50.6.9",
|
|
77
|
+
"eslint-plugin-prettier": "^5.2.6",
|
|
34
78
|
"prettier": "3.5.2",
|
|
35
79
|
"tsx": "^4.7.0",
|
|
36
|
-
"typescript": "^5.3.0"
|
|
37
|
-
|
|
80
|
+
"typescript": "^5.3.0",
|
|
81
|
+
"vitest": "^3.0.0"
|
|
82
|
+
},
|
|
83
|
+
"engines": {
|
|
84
|
+
"node": ">=18.0.0"
|
|
85
|
+
},
|
|
86
|
+
"license": "MIT"
|
|
38
87
|
}
|
package/.env-local
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
NETWORK=base-sepolia
|
|
2
|
-
EXPLORER_BASE_URL=https://sepolia.basescan.org
|
|
3
|
-
|
|
4
|
-
API_SERVER_URL=http://localhost:4021
|
|
5
|
-
CHECK_ROBOTS_API_PATH=/api/v1/free/preflight/url-check
|
|
6
|
-
METADATA_API_PATH=/api/v1/x402/extract/url-metadata
|
|
7
|
-
|
|
8
|
-
METADATA_URL=
|
|
9
|
-
METADATA_API_PARAM_INCLUDE_RESPONSE_BODY=
|
|
10
|
-
PRIVATE_KEY=
|
package/.prettierignore
DELETED
package/.prettierrc
DELETED
package/.tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ES2020",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"forceConsistentCasingInFileNames": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"baseUrl": ".",
|
|
12
|
-
"types": ["node"]
|
|
13
|
-
},
|
|
14
|
-
"include": ["index.ts"]
|
|
15
|
-
}
|