Skip to content

Commit

Permalink
SHOTS-4681: short-circuit for non-optimized skus (#21)
Browse files Browse the repository at this point in the history
* SHOTS-4681: short-circuit for non-optimized skus
  • Loading branch information
kkanwar authored Dec 1, 2023
1 parent b2c42c6 commit 2095d9f
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 6 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@spresso-sdk/price_optimization",
"version": "2.2.1",
"version": "2.3.0",
"description": "Spresso Price Optimization SDK for Node",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
69 changes: 64 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const AUTH_EXPIRATION_PAD_MS = 30 * 60 * 1000;
export interface ILogger {
error(message?: any, ...optionalParams: any[]): void;
info?(message?: any, ...optionalParams: any[]): void;
}
};

export type PricingRequest = {
defaultPrice?: number;
Expand Down Expand Up @@ -43,17 +43,23 @@ export type SDKOptions = {
type AuthResponse = {
access_token: string;
expires_in: number;
}
};

type UserAgent = {
name: string;
regexp: RegExp;
}
};

type UserAgentResponse = {
name: string;
regexp: string;
}
};

type OptimizedSkuResponse = {
expiresAt: number;
skus: string[];
skipInactive: boolean;
};

class SpressoSDK {
private authToken: string | null = null;
Expand All @@ -63,6 +69,9 @@ class SpressoSDK {
private readonly clientSecret: string;
private readonly logger: ILogger | undefined;
private tokenExpiration: number | null = null;
private optimizedSkus: Set<string>;
private skipInactive: boolean | null = null;
private skuExpiration: number | null = null;

constructor(options: SDKOptions) {
this.axiosInstance = axios.create({
Expand All @@ -82,18 +91,24 @@ class SpressoSDK {
timeout: options.keepAliveTimeoutMS ?? DEFAULT_KEEPALIVE_TIMEOUT_MS,
});

this.optimizedSkus = new Set();
this.logger = options.logger;
this.clientID = options.clientID;
this.clientSecret = options.clientSecret;

// Pre-emptively fetch auth token and bot user agents
// Pre-emptively fetch auth token, bot user agents and optimized skus
this.authenticate()
.then(() => this.getBotUserAgents())
.then(() => this.getOptimizedSkus())
.catch(() => {}); // intentional no-op
}

async getPrice(request: PricingRequest, userAgent: string | undefined): Promise<PricingResponse> {
try {
if (this.skipInactiveSkus([request])) {
return this.emptyResponse(request);
}

const response = await this.makeRequest(
'get',
request,
Expand All @@ -116,6 +131,10 @@ class SpressoSDK {

async getPrices(requests: PricingRequest[], userAgent: string | undefined): Promise<PricingResponse[]> {
try {
if (this.skipInactiveSkus(requests)) {
return this.emptyResponses(requests);
}

const response = await this.makeRequest(
'post',
undefined,
Expand All @@ -136,6 +155,24 @@ class SpressoSDK {
}
}

private skipInactiveSkus(requests: PricingRequest[]): boolean {
// Fetch optimized skus, don't await!
this.getOptimizedSkus().catch(() => {});

/* We only skip if ALL skus are non optimized
* Why? The point of this is to minimize the API roundtrip.
* If even one sku IS optimized we won't be able to skip, so no point adding the extra complexity of partial skips
*/
const someOptimized = requests.some(request => this.optimizedSkus.has(request.itemId));
if (this.skipInactive && !someOptimized) {
this.logInfo('All SKUs are non-optimized, short-circuiting...');
return true;
}

this.logInfo('Found optimized SKU, making API request...');
return false;
}

private async authenticate(): Promise<void> {
const now = new Date().getTime();
if (this.authToken != null && this.tokenExpiration != null && now < (this.tokenExpiration - AUTH_EXPIRATION_PAD_MS)) {
Expand Down Expand Up @@ -184,6 +221,28 @@ class SpressoSDK {
});
}

private async getOptimizedSkus(): Promise<void> {
const now = new Date().getTime();
if (this.skuExpiration != null && now < this.skuExpiration) {
return Promise.resolve(); // Optimized skus has already been fetched and has not expired
}

this.logInfo('Fetching optimized skus...');
return this.axiosInstance.request({
headers: {
'Authorization': this.authHeader()
},
method: 'get',
url: '/pim/v1/variants/optimizedSKUs',
}).then(response => {
const optimizedSkuResponse = (response.data as OptimizedSkuResponse);
this.logInfo(`${optimizedSkuResponse.skus.length} optimized skus fetched!`);
this.skuExpiration = optimizedSkuResponse.expiresAt * 1000;
this.optimizedSkus = new Set(optimizedSkuResponse.skus);
this.skipInactive = optimizedSkuResponse.skipInactive;
});
}

private async makeRequest(
method: string,
params: any | undefined,
Expand Down

0 comments on commit 2095d9f

Please sign in to comment.