@lorenstuff/amazon-selling-partner-api
Version:
A package for interacting with the Amazon Selling Partner API.
241 lines (182 loc) • 5.95 kB
text/typescript
//
// Imports
//
import { formatDate } from "../utilities/format-date.js";
//
// Class
//
/** An access token possessed by the client. */
export interface AmazonSellingPartnerAPIClientAccessToken
{
/** The access token. */
accessToken : string;
/** A unix timestamp representing when the access token expires. */
expiresTimestamp : number;
}
/** A response from Amazon's OAuth2 endpoint. */
export interface AmazonSellingPartnerAPIClientAccessTokenResponse
{
/** The access token. */
access_token : string;
/** The refresh token. */
refresh_token : string;
/** The token type. */
token_type : string;
/** The amount of time, in seconds, the access token is valid for. */
expires_in : number;
}
/** Options passed to the Client constructor. */
export interface AmazonSellingPartnerAPIClientOptions
{
/** A refresh token from an Amazon Seller Central app. */
refreshToken : string;
/** A client identifier from an Amazon Seller Central app. */
clientIdentifier : string;
/** A client secret from an Amazon Seller Central app. */
clientSecret : string;
/** @deprecated No longer used. */
iamUserAccessKey : string;
/** @deprecated No longer used. */
iamUserSecretAccessKey : string;
/**
* An Amazon Seller Partner API Endpoint to use.
*
* @see https://developer-docs.amazon.com/sp-api/docs/sp-api-endpoints
*/
apiEndpoint : string;
/**
* The AWS region to use. This should correspond to the region of the API endpoint.
*
* @see https://developer-docs.amazon.com/sp-api/docs/sp-api-endpoints
*/
awsRegion : string;
}
/** Options passed to a Client's connect method. */
export interface AmazonSellingPartnerAPIClientRequestOptions
{
/** The HTTP method to use. */
method : "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
/** The path to the API you want to call. */
path : string;
/** The query parameters to use, if any. */
searchParams? : URLSearchParams;
/** The body of the request, if any. */
body? : string;
}
/** The core client that handles actually connecting to the Selling Partner API. */
export class AmazonSellingPartnerAPIClient
{
/** The refresh token from an Amazon Seller Central app. */
refreshToken : string;
/** The client identifier from an Amazon Seller Central app. */
clientIdentifier : string;
/** The client secret from an Amazon Seller Central app. */
clientSecret : string;
/** The Amazon Seller Partner API Endpoint to use. */
apiEndpoint : string;
/** The AWS region to use. */
awsRegion : string;
/** The current access tokens this client has. */
accessTokens : AmazonSellingPartnerAPIClientAccessToken[];
/** Constructs a new client. */
constructor(options : AmazonSellingPartnerAPIClientOptions)
{
this.refreshToken = options.refreshToken;
this.clientIdentifier = options.clientIdentifier;
this.clientSecret = options.clientSecret;
this.apiEndpoint = options.apiEndpoint;
this.awsRegion = options.awsRegion;
this.accessTokens = [];
}
/**
* Adds an access token to the client.
*
* This is intended to be used to add a restricted data token.
*/
addAccessToken(accessToken : AmazonSellingPartnerAPIClientAccessToken) : AmazonSellingPartnerAPIClientAccessToken
{
this.accessTokens.unshift(accessToken);
return accessToken;
}
/** Removes an access token from the client, if it is still present. */
removeAccessToken(accessToken : AmazonSellingPartnerAPIClientAccessToken) : void
{
const index = this.accessTokens.indexOf(accessToken);
if (index === -1)
{
return;
}
this.accessTokens.splice(index, 1);
}
/**
* Fetches a fresh access token.
*
* @returns A promise that resolves to the access token.
*/
async getCurrentAccessToken() : Promise<AmazonSellingPartnerAPIClientAccessToken>
{
//
// Try Existing Tokens
//
while (this.accessTokens[0] != null)
{
const accessToken = this.accessTokens[0];
if (accessToken.expiresTimestamp > (Date.now() / 1000))
{
return accessToken;
}
this.accessTokens.shift();
}
//
// Request New Token
//
const headers = new Headers();
headers.set("Content-Type", "application/x-www-form-urlencoded");
const body = new URLSearchParams();
body.set("grant_type", "refresh_token");
body.set("refresh_token", this.refreshToken);
body.set("client_id", this.clientIdentifier);
body.set("client_secret", this.clientSecret);
const rawAccessTokenResponse = await fetch("https://api.amazon.com/auth/o2/token",
{
method: "POST",
headers,
body,
});
const accessTokenResponse = await rawAccessTokenResponse.json() as AmazonSellingPartnerAPIClientAccessTokenResponse;
if (accessTokenResponse.access_token == null)
{
throw new Error("Failed to fetch access token.");
}
//
// Add & Return Access Token
//
return this.addAccessToken(
{
accessToken: accessTokenResponse.access_token,
expiresTimestamp: (Date.now() / 1000) + accessTokenResponse.expires_in,
});
}
/** Performs a request to the Selling Partner API. */
async request(options: AmazonSellingPartnerAPIClientRequestOptions): Promise<Response>
{
const accessToken = await this.getCurrentAccessToken();
const dateTime = formatDate(new Date());
const headers = new Headers();
headers.set("host", this.apiEndpoint);
headers.set("user-agent", "Amazon SP API Node.js Client");
headers.set("x-amz-access-token", accessToken.accessToken);
headers.set("x-amz-date", dateTime);
let uri = this.apiEndpoint + options.path;
if (options.searchParams != null)
{
uri += "?" + options.searchParams.toString();
}
return await fetch(uri,
{
method: options.method,
headers,
body: options.body ?? null,
});
}
}