Creates a new instance of the supplier base class. Initializes the supplier with query parameters, request limits, and abort controller. Sets up logging and default product values.
The search term to query products for
The maximum number of results to return (default: 5)
Optionalcontroller: AbortControllerAbortController instance for managing request cancellation
// Create a supplier with default limit
const supplier = new MySupplier("sodium chloride", undefined, new AbortController());
// Create a supplier with custom limit
const supplier = new MySupplier("acetone", 10, new AbortController());
// Create a supplier and handle cancellation
const controller = new AbortController();
const supplier = new MySupplier("ethanol", 5, controller);
// Later, to cancel all pending requests:
controller.abort();
Initializes the cache for the supplier. This is called after construction to ensure supplierName is set.
ProtectedhttpRetrieves HTTP headers from a URL using a HEAD request. Useful for checking content types, caching headers, and other metadata without downloading the full response.
The URL to fetch headers from
Promise resolving to the response headers or void if request fails
ProtectedhttpSends a POST request to the given URL with the given body and headers. Handles request setup, error handling, and response caching.
The request configuration options
Promise resolving to the Response object or void if request fails
// Basic POST request
const response = await supplier.httpPost({
path: '/api/v1/products',
body: { name: 'Test Chemical' }
});
// POST with custom headers
const response = await supplier.httpPost({
path: '/api/v1/products',
body: { name: 'Test Chemical' },
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json'
}
});
ProtectedhttpSends a POST request and returns the response as a JSON object.
The parameters for the POST request.
The response from the POST request as a JSON object.
// Basic usage
const data = await supplier.httpPostJson({
path: '/api/v1/products',
body: { name: 'John' }
});
// With custom headers and error handling
try {
const data = await supplier.httpPostJson({
path: '/api/v1/products',
body: { name: 'John' },
headers: { 'Authorization': 'Bearer token123' }
});
if (data) {
console.log('Created:', data);
}
} catch (err) {
console.error('POST JSON failed:', err);
}
ProtectedhttpSends a POST request and returns the response as a HTML string.
The request configuration options
Promise resolving to the HTML response as a string or void if request fails
ProtectedhttpSends a GET request to the given URL with the specified options. Handles request setup, error handling, and response caching.
The request configuration options
Promise resolving to the Response object or void if request fails
// Basic GET request
const response = await supplier.httpGet({
path: '/products/search',
params: { query: 'sodium chloride' }
});
// GET with custom headers
const response = await supplier.httpGet({
path: '/products/search',
headers: { 'Accept': 'application/json' }
});
ProtectedfuzzyFilters an array of data using fuzzy string matching to find items that closely match a query string. Uses the WRatio algorithm from fuzzball for string similarity comparison.
The search string to match against
Array of data objects to search through
Minimum similarity score (0-100) for a match to be included (default: 40)
Array of matching data objects with added fuzzy match metadata
// Example with simple string array
const products = [
{ title: "Sodium Chloride", price: 29.99 },
{ title: "Sodium Hydroxide", price: 39.99 },
{ title: "Potassium Chloride", price: 19.99 }
];
const matches = this.fuzzyFilter("sodium chloride", products);
// Returns: [
// {
// title: "Sodium Chloride",
// price: 29.99,
// _fuzz: { score: 100, idx: 0 }
// },
// {
// title: "Sodium Hydroxide",
// price: 39.99,
// _fuzz: { score: 85, idx: 1 }
// }
// ]
// Example with custom cutoff
const strictMatches = this.fuzzyFilter("sodium chloride", products, 90);
// Returns only exact matches with score >= 90
// Example with different data structure
const chemicals = [
{ name: "NaCl", formula: "Sodium Chloride" },
{ name: "NaOH", formula: "Sodium Hydroxide" }
];
// Override titleSelector to use formula field
this.titleSelector = (data) => data.formula;
const formulaMatches = this.fuzzyFilter("sodium chloride", chemicals);
ProtectedhttpMakes an HTTP GET request and returns the response as a string. Handles request configuration, error handling, and HTML parsing.
The request configuration options
Promise resolving to the HTML response as a string or void if request fails
// Basic GET request
const html = await this.httpGetHtml({
path: "/api/products",
params: { search: "sodium" }
});
// GET request with custom headers
const html = await this.httpGetHtml({
path: "/api/products",
headers: {
"Authorization": "Bearer token123",
"Accept": "text/html"
}
});
// GET request with custom host
const html = await this.httpGetHtml({
path: "/products",
host: "api.supplier.com",
params: { limit: 10 }
});
ProtectedhttpMakes an HTTP GET request and returns the response as parsed JSON. Handles request configuration, error handling, and JSON parsing.
The request configuration options
Promise resolving to the parsed JSON response or void if request fails
// Basic GET request
const data = await supplier.httpGetJson({ path: '/api/products', params: { search: 'sodium' } });
// GET request with custom headers
const data = await supplier.httpGetJson({
path: '/api/products',
headers: {
'Authorization': 'Bearer token123',
'Accept': 'application/json'
}
});
ProtectedqueryExecutes a product search query with caching support. First checks the cache for existing results, then falls back to the actual query if needed. The limit parameter is only used for the actual query and doesn't affect caching.
The search term to query products for
The maximum number of results to return (defaults to instance limit)
Promise resolving to array of product builders or void if search fails
Executes the supplier's search query and returns the results. This method will execute all results concurrently (to the limits set in the supplier class), and resolve to an array of product objects.
Promise resolving to an array of products
ProtectedfinishFinalizes a partial product by adding computed properties and validating the result. This method:
The ProductBuilder instance containing the partial product to finalize
Promise resolving to a complete Product object or void if validation fails
// Example with a valid partial product
const builder = new ProductBuilder<Product>(this.baseURL);
builder
.setBasicInfo("Sodium Chloride", "/products/nacl", "ChemSupplier")
.setPricing(29.99, "USD", "$")
.setQuantity(500, "g");
const finishedProduct = await this.finishProduct(builder);
if (finishedProduct) {
console.log("Finalized product:", {
title: finishedProduct.title,
price: finishedProduct.price,
quantity: finishedProduct.quantity,
uom: finishedProduct.uom,
usdPrice: finishedProduct.usdPrice,
baseQuantity: finishedProduct.baseQuantity
});
}
// Example with an invalid partial product
const invalidBuilder = new ProductBuilder<Product>(this.baseURL);
invalidBuilder.setBasicInfo("Sodium Chloride", "/products/nacl", "ChemSupplier");
// Missing required fields
const invalidProduct = await this.finishProduct(invalidBuilder);
if (!invalidProduct) {
console.log("Failed to finalize product - missing required fields");
}
ProtectedhrefTakes in either a relative or absolute URL and returns an absolute URL. This is useful for when you aren't sure if the link (retrieved from parsed text, a setting, an element, an anchor value, etc) is absolute or not. Using relative links will result in http://chrome-extension://... being added to the link.
URL object or string
Optionalparams: Maybe<RequestParams>The parameters to add to the URL.
Optionalhost: stringThe host to use for overrides (eg: needing to call a different host for an API)
absolute URL
this.href('/some/path')
// https://supplier_base_url.com/some/path
this.href('https://supplier_base_url.com/some/path', null, 'another_host.com')
// https://another_host.com/some/path
this.href('/some/path', { a: 'b', c: 'd' }, 'another_host.com')
// http://another_host.com/some/path?a=b&c=d
this.href('https://supplier_base_url.com/some/path')
// https://supplier_base_url.com/some/path
this.href(new URL('https://supplier_base_url.com/some/path'))
// https://supplier_base_url.com/some/path
this.href('/some/path', { a: 'b', c: 'd' })
// https://supplier_base_url.com/some/path?a=b&c=d
this.href('https://supplier_base_url.com/some/path', new URLSearchParams({ a: 'b', c: 'd' }))
// https://supplier_base_url.com/some/path?a=b&c=d
ProtectedgetRetrieves product data with caching support. Similar to getProductData but allows for additional parameters to be included in the cache key.
The ProductBuilder instance to get data for
The function to use for fetching product data
Optionalparams: Record<string, string>Optional parameters to include in the cache key
Promise resolving to the updated ProductBuilder or void if fetch fails
const builder = new ProductBuilder<Product>(this.baseURL);
builder.setBasicInfo("Acetone", "/products/acetone", "ChemSupplier");
// Use custom fetcher with additional params
const updatedBuilder = await supplier.getProductDataWithCache(
builder,
async (b) => {
// Custom fetching logic
return b;
},
{ version: "2.0" }
);
ProtectedgroupProtectedfetchInternal fetch method with request counting and decorator. Tracks request count and enforces hard limits on HTTP requests.
Arguments to pass to fetchDecorator (usually a Request or URL and options)
The response from the fetchDecorator
ProtectedsetupSets up the Macklin API client by:
void
ProtectedqueryQueries the Macklin API for products matching the search term. Handles the complex response structure where products are grouped by CAS number and may have multiple variants per CAS number.
The search term to find products
Maximum number of products to return (after fuzzy filtering a search limited to 90 results))
Array of ProductBuilder instances or void if the request fails
ProtectedtitleExtracts the English name from a Macklin product variant. Used by the base class to display product titles.
The product variant to extract the title from
The English name of the product
ProtectedgetFetches detailed product information from the Macklin API. This includes pricing, stock levels, and delivery information that isn't available in the search results.
The ProductBuilder instance to enrich with details
The enriched ProductBuilder or void if the request fails
ProtectedinitCreates ProductBuilder instances from Macklin product variants. This is the final step in the product search process, converting the API response into a format that can be used by the rest of the application.
Array of product variants to convert
Array of ProductBuilder instances
PrivategenerateGenerates a random string for use in device IDs and user tokens. Can operate in two modes:
Optionallength: numberOptional length for random string mode
OptionalcharSetSize: numberOptional size of character set to use
A random string
PrivatesignStep 1 & 2: Signature Generation Implements the core signature generation process by:
Request headers to sign
Request parameters to sign
The final request signature
PrivaterequestStep 3: Request Processing The main request handler that:
The API endpoint to call
Request configuration
The API response
PrivatefetchStep 4.1: Server Timestamp Management Fetches and stores the server timestamp to maintain time synchronization. Called when local timestamp is missing or expired (over 800 seconds old, or 13 minutes).
The server timestamp
PrivategenerateStep 4.2: Request Timestamp Generation Generates a unique timestamp for each request using either:
A unique timestamp string for the request
PrivatevalidateStep 4.3: Timestamp Validation and Update Manages the timestamp lifecycle:
The current valid timestamp as a string
PrivateensureEnsures header values are always strings, handling arrays and null values. This prevents issues with header concatenation and type mismatches.
The header value to convert
A string representation of the value
ProtectedqueryString to query for (Product name, CAS, etc). This is the search term that will be used to find products. Set during construction and used throughout the supplier's lifecycle.
ProtectedbaseThe base search parameters that are always included in search requests. These parameters are merged with any additional search parameters when making requests to the supplier's API.
ProtectedcontrollerThe AbortController instance used to manage and cancel ongoing requests. This allows for cancellation of in-flight requests when needed, such as when a new search is started or the supplier is disposed.
ProtectedlimitThe maximum number of results to return for a search query. This is not a limit on HTTP requests, but rather the number of products that will be returned to the caller.
ProtectedproductsThe products that are currently being built by the supplier. This array holds ProductBuilder instances that are in the process of being transformed into complete Product objects.
ProtectedhttpMaximum number of HTTP requests allowed per search query. This is a hard limit to prevent excessive requests to the supplier's API. If this limit is reached, the supplier will stop making new requests.
ProtectedrequestCounter for HTTP requests made during the current query execution. This is used to track the number of requests and ensure we don't exceed the httpRequestHardLimit.
ProtectedmaxNumber of requests to process in parallel when fetching product details. This controls the batch size for concurrent requests to avoid overwhelming the supplier's API and the user's bandwidth.
ProtectedminMinimum number of milliseconds between two consecutive tasks
ProtectedloggerProtectedproductProtected Static ReadonlyproductProtectedcacheReadonlysupplierName of supplier (for display purposes)
ReadonlybaseBase URL for HTTP(s) requests
ReadonlyapiThe host of the Macklin API.
ReadonlyshippingShipping scope for Macklin
ReadonlycountryThe country code of the supplier.
ReadonlypaymentThe payment methods accepted by the supplier. This is used to determine the payment methods accepted by the supplier.
ProtectedqueryOverride the type of queryResults to use our specific type
ProtectedhttpUsed to keep track of how many requests have been made to the supplier.
Private ReadonlyTIMESTAMP_Private ReadonlySALTThe salt used to sign requests.
Private ReadonlyDEFAULT_PrivatelocalLocal storage object for the Macklin API client.
PrivatelastThe last signature used for the request
ProtectedheadersHTTP headers used as a basis for all queries.
Macklin is a Chineese based chemical supply company. This module handles API requests with custom authentication and request signing for every call.
Remarks
Macklins client side code is very different from the other platforms. Looking at the API structure and authentication pattern, this appears to be a custom implementation rather than a standard ecommerce platform or CMS. The
/api/timestampendpoint with the specific authentication flow (using device IDs, custom signing with salt, and timestamp synchronization) is not a common pattern in major platforms. The specific implementation (withMklTmKeyinlocalStorage, the saltndksyr9834@#$32ndsfu, and the custom MD5-like transformation) suggests this is a custom-built platform, likely Macklin's own ecommerce system, rather than a standard off-the-shelf solution.Request Signature Generation Process
The Macklin API requires a custom signature for each request to ensure authenticity. The signature is generated through the following steps:
Step 1: Header String Generation
Step 2: Parameter String Generation
Step 3: Final Signature
Step 4: Timestamp Handling
Example