@congminh1254/shopee-sdk
Version:
Shopee SDK maintaining by community
183 lines • 7.29 kB
JavaScript
import fetch, { Blob, FormData, Headers } from "node-fetch";
import { ShopeeApiError, ShopeeSdkError } from "./errors.js";
import { generateSignature } from "./utils/signature.js";
import { SDK_VERSION } from "./version.js";
function isBlobLike(value) {
return (value instanceof Blob ||
(typeof value === "object" &&
value !== null &&
["Blob", "File"].includes(value.constructor?.name ?? "")));
}
function isBinaryLike(value) {
return Buffer.isBuffer(value) || isBlobLike(value);
}
function hasBinaryValue(value) {
return Array.isArray(value) ? value.some(hasBinaryValue) : isBinaryLike(value);
}
function isFormDataBody(body) {
return body instanceof FormData;
}
function appendFormValue(formData, key, value) {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((item) => appendFormValue(formData, key, item));
return;
}
if (Buffer.isBuffer(value)) {
formData.append(key, new Blob([value]), `${key}.bin`);
return;
}
if (isBlobLike(value)) {
const filename = "name" in value && typeof value.name === "string" ? value.name : undefined;
if (filename) {
formData.append(key, value, filename);
return;
}
formData.append(key, value);
return;
}
if (typeof value === "object") {
formData.append(key, JSON.stringify(value));
return;
}
formData.append(key, String(value));
}
function serializeRequestBody(body) {
if (body === undefined) {
return { body: undefined, isMultipart: false };
}
if (isFormDataBody(body)) {
return { body: body, isMultipart: true };
}
if (typeof body === "object" && body !== null && !Array.isArray(body)) {
const bodyEntries = Object.entries(body);
if (bodyEntries.some(([, value]) => hasBinaryValue(value))) {
const formData = new FormData();
bodyEntries.forEach(([key, value]) => appendFormValue(formData, key, value));
return { body: formData, isMultipart: true };
}
}
return { body: JSON.stringify(body), isMultipart: false };
}
export class ShopeeFetch {
static async fetch(config, path, options = {}) {
const { method = "GET", params = {}, body } = options;
const url = new URL(`${config.base_url}${path}`);
// Add required parameters
const timestamp = Math.floor(Date.now() / 1000);
let signature = generateSignature(config.partner_key, [
config.partner_id.toString(),
url.pathname,
timestamp.toString(),
]);
// Add query parameters
Object.keys(params).forEach((key) => (params[key] === undefined ? delete params[key] : {}));
const allParams = {
partner_id: config.partner_id,
timestamp,
...params,
};
let authParams = {};
if (options.auth) {
let token = await config.sdk?.getAuthToken();
if (token?.expired_at && token.expired_at < Date.now()) {
token = await config.sdk?.refreshToken();
}
if (!token) {
throw new ShopeeSdkError("No access token found");
}
authParams = {
access_token: token?.access_token,
shop_id: token?.shop_id,
};
signature = generateSignature(config.partner_key, [
config.partner_id.toString(),
url.pathname,
timestamp.toString(),
token?.access_token,
token?.shop_id.toString(),
]);
}
Object.entries({ ...allParams, ...authParams, sign: signature }).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((item) => {
url.searchParams.append(key, String(item));
});
}
else {
url.searchParams.append(key, String(value));
}
});
const { body: requestBody, isMultipart } = serializeRequestBody(body);
// Prepare headers
const headers = new Headers();
if (!isMultipart) {
headers.set("Content-Type", "application/json");
}
headers.set("User-Agent", `congminh1254/shopee-sdk/v${SDK_VERSION}`);
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
headers.set(key, value);
});
}
// Prepare fetch options
const requestOptions = {
method,
headers: headers,
body: requestBody,
agent: config.agent,
};
try {
const response = await fetch(url.toString(), requestOptions);
const responseType = response.headers.get("Content-Type") || response.headers.get("content-type") || "";
if (responseType.includes("application/pdf") ||
responseType.includes("application/octet-stream")) {
const arrayBuffer = await response.arrayBuffer();
return Buffer.from(arrayBuffer);
}
const isJson = !responseType || !!responseType.toLowerCase().includes("json");
const responseData = isJson ? await response.json() : await response.text();
if (isJson) {
// Type guard for JSON response with error field
const jsonData = responseData;
if (jsonData.error) {
// Handle invalid access token error
if (jsonData.error === "invalid_acceess_token" && options.auth) {
try {
// Attempt to refresh the access token
await config.sdk?.refreshToken();
// Retry the request with the new token
return this.fetch(config, path, options);
}
catch {
// If refresh fails, throw the original error
throw new ShopeeApiError(response.status, jsonData);
}
}
throw new ShopeeApiError(response.status, jsonData);
}
const data = responseData;
return data;
}
throw new ShopeeSdkError(`Unknown response type: ${responseType}\n${responseData}`);
}
catch (error) {
if (error instanceof Error) {
// Re-throw our custom errors as-is
if (error instanceof ShopeeApiError || error instanceof ShopeeSdkError) {
throw error;
}
if (error.name === "FetchError") {
// Network error
throw new ShopeeSdkError(`Network error: ${error.message}`);
}
// Other errors
throw new ShopeeSdkError(`Unexpected error: ${error.message}`);
}
throw new ShopeeSdkError("Unknown error occurred");
}
}
}
//# sourceMappingURL=fetch.js.map