@saleor/app-sdk
Version:
SDK for building great Saleor Apps
330 lines (322 loc) • 11 kB
JavaScript
import {
OTEL_APL_SERVICE_NAME,
getOtelTracer
} from "../../chunk-UCTHLSSD.mjs";
import {
createAPLDebug
} from "../../chunk-ORQVZRNL.mjs";
import "../../chunk-CPDLIPGD.mjs";
// src/APL/saleor-cloud/saleor-cloud-apl.ts
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
import { SemanticAttributes } from "@opentelemetry/semantic-conventions";
// src/has-prop.ts
function hasProp(obj, key) {
return key != null && obj != null && typeof obj === "object" && key in obj;
}
// src/APL/has-auth-data.ts
var hasAuthData = (data) => hasProp(data, "token") && data.token && hasProp(data, "appId") && data.appId && hasProp(data, "saleorApiUrl") && data.saleorApiUrl;
// src/APL/auth-data-from-object.ts
var debug = createAPLDebug("authDataFromObject");
var authDataFromObject = (parsed) => {
if (!hasAuthData(parsed)) {
debug("Given object did not contained AuthData");
return void 0;
}
const { saleorApiUrl, appId, token, jwks } = parsed;
return {
saleorApiUrl,
appId,
token,
jwks
};
};
// src/APL/saleor-cloud/paginator.ts
var debug2 = createAPLDebug("Paginator");
var Paginator = class {
constructor(url, fetchOptions, fetchFn = fetch) {
this.url = url;
this.fetchOptions = fetchOptions;
this.fetchFn = fetchFn;
}
async fetchAll() {
debug2("Fetching all pages for url", this.url);
const response = await this.fetchFn(this.url, this.fetchOptions);
debug2("%0", response);
const json = await response.json();
if (json.next) {
const remainingPages = await this.fetchNext(json.next);
const allResults = [...json.results, ...remainingPages.flatMap((page) => page.results)];
debug2("Fetched all pages, total length: %d", allResults.length);
return {
next: null,
previous: null,
count: allResults.length,
results: allResults
};
}
debug2("No more pages to fetch, returning first page");
return json;
}
async fetchNext(nextUrl) {
debug2("Fetching next page with url %s", nextUrl);
const response = await this.fetchFn(nextUrl, this.fetchOptions);
debug2("%0", response);
const json = await response.json();
if (json.next) {
return [json, ...await this.fetchNext(json.next)];
}
return [json];
}
};
// src/APL/saleor-cloud/saleor-cloud-apl-errors.ts
var SaleorCloudAplError = class extends Error {
constructor(code, message) {
super(message);
this.code = code;
this.name = "SaleorCloudAplError";
}
};
var CloudAplError = {
FAILED_TO_REACH_API: "FAILED_TO_REACH_API",
RESPONSE_BODY_INVALID: "RESPONSE_BODY_INVALID",
RESPONSE_NON_200: "RESPONSE_NON_200",
ERROR_SAVING_DATA: "ERROR_SAVING_DATA",
ERROR_DELETING_DATA: "ERROR_DELETING_DATA"
};
// src/APL/saleor-cloud/saleor-cloud-apl.ts
var debug3 = createAPLDebug("SaleorCloudAPL");
var validateResponseStatus = (response) => {
if (!response.ok) {
debug3("Response failed with status %s", response.status);
debug3("%O", response);
throw new SaleorCloudAplError(
CloudAplError.RESPONSE_NON_200,
`Fetch returned with non 200 status code ${response.status}`
);
}
};
var mapAuthDataToAPIBody = (authData) => ({
saleor_app_id: authData.appId,
saleor_api_url: authData.saleorApiUrl,
jwks: authData.jwks,
token: authData.token,
domain: new URL(authData.saleorApiUrl).hostname
});
var mapAPIResponseToAuthData = (response) => ({
appId: response.saleor_app_id,
jwks: response.jwks,
saleorApiUrl: response.saleor_api_url,
token: response.token
});
var extractErrorMessage = (error) => {
if (typeof error === "string") {
return error;
}
if (hasProp(error, "message")) {
return error.message;
}
return "Unknown error";
};
var SaleorCloudAPL = class {
constructor(config) {
this.resourceUrl = config.resourceUrl;
this.headers = {
Authorization: `Bearer ${config.token}`
};
this.tracer = getOtelTracer();
this.cacheManager = config?.experimental?.cacheManager;
this.pageLimit = config.pageLimit ?? 1e3;
}
getUrlForDomain(saleorApiUrl) {
return `${this.resourceUrl}/${Buffer.from(saleorApiUrl).toString("base64url")}`;
}
getUrlWithLimit() {
return `${this.resourceUrl}?limit=${this.pageLimit}`;
}
setToCacheIfExists(saleorApiUrl, authData) {
if (!this.cacheManager) {
return;
}
this.cacheManager.set(authData.saleorApiUrl, authData);
}
deleteFromCacheIfExists(saleorApiUrl) {
if (!this.cacheManager) {
return;
}
this.cacheManager.delete(saleorApiUrl);
}
getFromCacheIfExists(saleorApiUrl) {
return this.cacheManager?.get(saleorApiUrl);
}
async get(saleorApiUrl) {
const cachedData = this.getFromCacheIfExists(saleorApiUrl);
if (cachedData) {
debug3("Returning authData from cache for saleorApiUrl %s", saleorApiUrl);
return cachedData;
}
debug3("Will fetch data from SaleorCloudAPL for saleorApiUrl %s", saleorApiUrl);
return this.tracer.startActiveSpan(
"SaleorCloudAPL.get",
{
attributes: {
saleorApiUrl,
[SemanticAttributes.PEER_SERVICE]: OTEL_APL_SERVICE_NAME
},
kind: SpanKind.CLIENT
},
async (span) => {
const response = await fetch(this.getUrlForDomain(saleorApiUrl), {
method: "GET",
headers: { "Content-Type": "application/json", ...this.headers }
}).catch((error) => {
debug3("Failed to reach API call: %s", extractErrorMessage(error));
debug3("%O", error);
span.recordException(CloudAplError.FAILED_TO_REACH_API);
span.setStatus({
code: SpanStatusCode.ERROR,
message: extractErrorMessage(error)
}).end();
throw new SaleorCloudAplError(
CloudAplError.FAILED_TO_REACH_API,
`${extractErrorMessage(error)}`
);
});
if (!response) {
debug3("No response from the API");
span.recordException(CloudAplError.FAILED_TO_REACH_API);
span.setStatus({
code: SpanStatusCode.ERROR,
message: "Response couldn't be resolved"
}).end();
throw new SaleorCloudAplError(
CloudAplError.FAILED_TO_REACH_API,
"Response couldn't be resolved"
);
}
if (response.status >= 500) {
const message = `Api responded with ${response.status}`;
span.recordException(CloudAplError.FAILED_TO_REACH_API);
span.setStatus({
code: SpanStatusCode.ERROR,
message
}).end();
throw new SaleorCloudAplError(CloudAplError.FAILED_TO_REACH_API, message);
}
if (response.status === 404) {
debug3("No auth data for given saleorApiUrl");
span.addEvent("Missing auth data for given saleorApiUrl");
span.setStatus({
code: SpanStatusCode.OK
}).end();
return void 0;
}
const parsedResponse = await response.json().catch((e) => {
debug3("Failed to parse response: %s", extractErrorMessage(e));
debug3("%O", e);
const message = `Cant parse response body: ${extractErrorMessage(e)}`;
span.recordException(CloudAplError.RESPONSE_BODY_INVALID);
span.setStatus({
code: SpanStatusCode.ERROR,
message
}).end();
throw new SaleorCloudAplError(CloudAplError.RESPONSE_BODY_INVALID, message);
});
const authData = authDataFromObject(mapAPIResponseToAuthData(parsedResponse));
if (!authData) {
debug3("No auth data for given saleorApiUrl");
span.addEvent("Missing auth data for given saleorApiUrl");
span.setStatus({
code: SpanStatusCode.OK
}).end();
return void 0;
}
span.setAttribute("appId", authData.appId);
this.setToCacheIfExists(authData.saleorApiUrl, authData);
span.setStatus({
code: SpanStatusCode.OK
}).end();
return authData;
}
);
}
async set(authData) {
debug3("Saving data to SaleorCloudAPL for saleorApiUrl: %s", authData.saleorApiUrl);
return this.tracer.startActiveSpan(
"SaleorCloudAPL.set",
{
attributes: {
saleorApiUrl: authData.saleorApiUrl,
appId: authData.appId,
[SemanticAttributes.PEER_SERVICE]: OTEL_APL_SERVICE_NAME
},
kind: SpanKind.CLIENT
},
async (span) => {
const response = await fetch(this.resourceUrl, {
method: "POST",
headers: { "Content-Type": "application/json", ...this.headers },
body: JSON.stringify(mapAuthDataToAPIBody(authData))
}).catch((e) => {
debug3("Failed to reach API call: %s", extractErrorMessage(e));
debug3("%O", e);
span.recordException(`Failed to reach API call: ${extractErrorMessage(e)}`);
span.setStatus({
code: SpanStatusCode.ERROR
}).end();
throw new SaleorCloudAplError(
CloudAplError.ERROR_SAVING_DATA,
`Error during saving the data: ${extractErrorMessage(e)}`
);
});
validateResponseStatus(response);
debug3("Set command finished successfully for saleorApiUrl: %", authData.saleorApiUrl);
this.setToCacheIfExists(authData.saleorApiUrl, authData);
span.setStatus({
code: SpanStatusCode.OK
});
span.end();
return void 0;
}
);
}
async delete(saleorApiUrl) {
debug3("Deleting data from SaleorCloud for saleorApiUrl: %s", saleorApiUrl);
try {
const response = await fetch(this.getUrlForDomain(saleorApiUrl), {
method: "DELETE",
headers: { "Content-Type": "application/json", ...this.headers }
});
this.deleteFromCacheIfExists(saleorApiUrl);
debug3(`Delete responded with ${response.status} code`);
} catch (error) {
const errorMessage = extractErrorMessage(error);
debug3("Error during deleting the data: %s", errorMessage);
debug3("%O", error);
throw new SaleorCloudAplError(
CloudAplError.ERROR_DELETING_DATA,
`Error during deleting the data: ${errorMessage}`
);
}
}
async getAll() {
debug3("Get all data from SaleorCloud");
try {
const paginator = new Paginator(this.getUrlWithLimit(), {
method: "GET",
headers: { "Content-Type": "application/json", ...this.headers }
});
const responses = await paginator.fetchAll();
return responses.results.map(mapAPIResponseToAuthData);
} catch (error) {
const errorMessage = extractErrorMessage(error);
debug3("Error during getting all the data:", errorMessage);
debug3("%O", error);
}
return [];
}
};
export {
CloudAplError,
SaleorCloudAPL,
SaleorCloudAplError
};