@sp-api-sdk/auth
Version:
Login with Amazon (LWA) authentication for the Amazon Selling Partner API (SP-API)
124 lines (119 loc) • 4.43 kB
JavaScript
import process from "node:process";
import globalAxios, { AxiosError } from "axios";
import { sync } from "read-pkg-up";
//#region src/error.ts
/**
Error thrown when an LWA token request fails.
Wraps the underlying Axios error with a human-readable message that includes
the HTTP status code (or "No response" for network errors).
*/
var SellingPartnerApiAuthError = class extends AxiosError {
/** The original error message from the failed HTTP request. */
innerMessage;
constructor(error) {
const message = error.response ? `access-token error: Response code ${error.response.status}` : "access-token error: No response";
super(message, error.code, error.config, error.request, error.response);
this.innerMessage = error.message;
this.name = this.constructor.name;
}
};
const packageJson = sync()?.packageJson ?? {
_id: "",
readme: "",
name: "@sp-api-sdk/auth",
version: "unknown"
};
//#endregion
//#region src/utils/axios.ts
const axios = globalAxios.create({
baseURL: "https://api.amazon.com/auth/",
timeout: 3e4,
headers: { "user-agent": `${packageJson.name}/${packageJson.version}` }
});
//#endregion
//#region src/types/scope.ts
/** Authorization scopes for grantless Selling Partner API operations. */
let AuthorizationScope = /* @__PURE__ */ function(AuthorizationScope) {
/** Scope for the Notifications API. */
AuthorizationScope["NOTIFICATIONS"] = "sellingpartnerapi::notifications";
/** Scope for rotating application client credentials. */
AuthorizationScope["CLIENT_CREDENTIAL_ROTATION"] = "sellingpartnerapi::client_credential:rotation";
return AuthorizationScope;
}({});
//#endregion
//#region src/index.ts
/**
Handles Login with Amazon (LWA) OAuth token management for the Selling Partner API.
Supports both refresh-token and grantless (scope-based) authentication flows.
Tokens are cached and automatically refreshed when expired. Concurrent calls
to {@link getAccessToken} are deduplicated into a single request.
*/
var SellingPartnerApiAuth = class {
clientId;
clientSecret;
refreshToken;
scopes;
#accessToken;
#accessTokenExpiration;
#pendingTokenRequest;
constructor(parameters) {
const clientId = parameters.clientId ?? process.env.LWA_CLIENT_ID;
if (!clientId) throw new Error("Missing required `clientId` configuration value");
const clientSecret = parameters.clientSecret ?? process.env.LWA_CLIENT_SECRET;
if (!clientSecret) throw new Error("Missing required `clientSecret` configuration value");
const refreshToken = parameters.refreshToken ?? process.env.LWA_REFRESH_TOKEN;
if (!refreshToken && !parameters.scopes) throw new TypeError("Either `refreshToken` or `scopes` must be specified");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.refreshToken = refreshToken;
this.scopes = parameters.scopes;
}
async #refreshAccessToken() {
const body = {
client_id: this.clientId,
client_secret: this.clientSecret,
...this.refreshToken ? {
grant_type: "refresh_token",
refresh_token: this.refreshToken
} : {
grant_type: "client_credentials",
scope: this.scopes.join(" ")
}
};
try {
const expiration = /* @__PURE__ */ new Date();
const { data } = await axios.post("/o2/token", body);
expiration.setSeconds(expiration.getSeconds() + data.expires_in);
this.#accessToken = data.access_token;
this.#accessTokenExpiration = expiration;
return data.access_token;
} catch (error) {
if (error instanceof AxiosError) throw new SellingPartnerApiAuthError(error);
throw error;
}
}
/**
Returns a valid LWA access token, refreshing it if expired.
Concurrent calls while a refresh is in progress share the same request.
@returns The access token string.
*/
async getAccessToken() {
if (this.#accessToken && (!this.#accessTokenExpiration || Date.now() < this.#accessTokenExpiration.getTime())) return this.#accessToken;
if (this.#pendingTokenRequest) return this.#pendingTokenRequest;
this.#pendingTokenRequest = this.#refreshAccessToken();
try {
return await this.#pendingTokenRequest;
} finally {
this.#pendingTokenRequest = void 0;
}
}
/**
Expiration date of the currently cached access token, or `undefined` if no token has been fetched yet.
*/
get accessTokenExpiration() {
return this.#accessTokenExpiration;
}
};
//#endregion
export { AuthorizationScope, SellingPartnerApiAuth, SellingPartnerApiAuthError };
//# sourceMappingURL=index.js.map