Skip to content

Commit 55d36d9

Browse files
Merge branch 'main' into multi-extension-support
2 parents 546e0a2 + 561941f commit 55d36d9

File tree

8 files changed

+265
-29
lines changed

8 files changed

+265
-29
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77

8+
---
9+
## [v0.6.0] - 2024-01-09
10+
### Added
11+
- Added retry mechanism for APIs getting used inside extension library if Fynd Platform server is down.
812
---
913
## [v0.5.4] - 2023-03-03
1014
### Changed

express/constants.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ exports.ASSOCIATION_CRITERIA = {
66
ALL: "ALL",
77
SPECIFIC: "SPECIFIC-EVENTS", // to be set when saleschannel specific events are subscribed and sales channel present
88
EMPTY: "EMPTY" // to be set when saleschannel specific events are subscribed but not sales channel present
9-
}
9+
}
10+
exports.TIMEOUT_STATUS = 504;
11+
exports.SERVICE_UNAVAILABLE = 503;
12+
exports.BAD_GATEWAY = 502;

express/extension.js

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const logger = require('./logger');
88
const { fdkAxios } = require('@gofynd/fdk-client-javascript/sdk/common/AxiosHelper');
99
const { version } = require('./../package.json');
1010
const SessionStorage = require("./session/session_storage");
11+
const { RetryManger } = require("./retry_manager")
1112

1213
class Extension {
1314
constructor() {
@@ -22,11 +23,17 @@ class Extension {
2223
this.sessionStore = null;
2324
this._isInitialized = false;
2425
this._clusterId = null;
26+
this._retryManager = new RetryManger();
2527
}
2628

2729
async initialize(data) {
2830

31+
if (this._isInitialized) {
32+
return;
33+
}
34+
2935
this._isInitialized = false;
36+
this.configData = data;
3037

3138
this.storage = data.storage;
3239

@@ -55,22 +62,22 @@ class Extension {
5562
this.cluster = data.cluster;
5663
this._clusterId = this.cluster.replace("https://", "");
5764
}
58-
this.webhookRegistry = new WebhookRegistry();
65+
this.webhookRegistry = new WebhookRegistry(this._retryManager);
5966

60-
let extensionData = await this.getExtensionDetails();
67+
await this.getExtensionDetails();
6168

6269
if (data.base_url && !validator.isURL(data.base_url)) {
6370
throw new FdkInvalidExtensionConfig("Invalid base_url value. Invalid value: " + data.base_url);
6471
}
6572
else if (!data.base_url) {
66-
data.base_url = extensionData.base_url;
73+
data.base_url = this.extensionData.base_url;
6774
}
6875
this.base_url = data.base_url;
6976

7077
if (data.scopes) {
71-
data.scopes = this.verifyScopes(data.scopes, extensionData);
78+
data.scopes = this.verifyScopes(data.scopes, this.extensionData);
7279
}
73-
this.scopes = data.scopes || extensionData.scope;
80+
this.scopes = data.scopes || this.extensionData.scope;
7481

7582
logger.debug(`Extension initialized`);
7683

@@ -105,9 +112,9 @@ class Extension {
105112
return this.access_mode === 'online';
106113
}
107114

108-
getPlatformConfig(companyId) {
115+
async getPlatformConfig(companyId) {
109116
if (!this._isInitialized){
110-
throw new FdkInvalidExtensionConfig('Extension not initialized due to invalid data')
117+
await this.initialize(this.configData);
111118
}
112119
let platformConfig = new PlatformConfig({
113120
companyId: parseInt(companyId),
@@ -122,10 +129,10 @@ class Extension {
122129

123130
async getPlatformClient(companyId, session) {
124131
if (!this._isInitialized){
125-
throw new FdkInvalidExtensionConfig('Extension not initialized due to invalid data')
132+
await this.initialize(this.configData);
126133
}
127134

128-
let platformConfig = this.getPlatformConfig(companyId);
135+
let platformConfig = await this.getPlatformConfig(companyId);
129136
platformConfig.oauthClient.setToken(session);
130137
platformConfig.oauthClient.token_expires_at = session.access_token_validity;
131138

@@ -148,14 +155,22 @@ class Extension {
148155
}
149156

150157
async getExtensionDetails() {
158+
159+
let url = `${this.cluster}/service/panel/partners/v1.0/extensions/details/${this.api_key}`;
160+
const uniqueKey = `${url}`;
161+
162+
const retryInfo = this._retryManager.retryInfoMap.get(uniqueKey);
163+
if (retryInfo && !retryInfo.isRetry) {
164+
this._retryManager.resetRetryState(uniqueKey);
165+
}
166+
151167
try {
152-
let url = `${this.cluster}/service/panel/partners/v1.0/extensions/details/${this.api_key}`;
153168
const token = Buffer.from(
154169
`${this.api_key}:${this.api_secret}`,
155170
"utf8"
156171
).toString("base64");
157172
const rawRequest = {
158-
method: "get",
173+
method: "GET",
159174
url: url,
160175
headers: {
161176
Authorization: `Basic ${token}`,
@@ -165,9 +180,18 @@ class Extension {
165180
};
166181
let extensionData = await fdkAxios.request(rawRequest);
167182
logger.debug(`Extension details received: ${logger.safeStringify(extensionData)}`);
168-
return extensionData;
183+
this.extensionData = extensionData;
169184
} catch (err) {
170-
throw new FdkInvalidExtensionConfig("Invalid api_key or api_secret. Reason: " + err.message);
185+
186+
if (
187+
RetryManger.shouldRetryOnError(err)
188+
&& !this._retryManager.isRetryInProgress(uniqueKey)
189+
) {
190+
logger.debug(`API call failed. Starting retry for ${uniqueKey}`)
191+
return await this._retryManager.retry(uniqueKey, this.getExtensionDetails.bind(this));
192+
}
193+
194+
throw new FdkInvalidExtensionConfig("Invalid api_key or api_secret. Reason:" + err.message);
171195
}
172196
}
173197
}

express/retry_manager.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
const { BAD_GATEWAY, SERVICE_UNAVAILABLE, TIMEOUT_STATUS } = require("./constants");
2+
const logger = require('./logger');
3+
4+
5+
/**
6+
* @typedef {Object} RetryInfo
7+
* @property {boolean} isRetryInProgress
8+
* @property {boolean} isRetry
9+
* @property {number} retryCount
10+
* @property {NodeJS.Timeout | null} retryTimer
11+
* @property {function} fn
12+
* @property {any[]} args
13+
*/
14+
15+
/**
16+
* @typedef {Map<string, RetryInfo>} RetryInfoMap
17+
*/
18+
19+
class RetryManger {
20+
21+
constructor() {
22+
/**
23+
* @type {RetryInfoMap}
24+
*/
25+
this.retryInfoMap = new Map();
26+
}
27+
28+
static shouldRetryOnError(err) {
29+
const statusCode = (err.response && err.response.status) || err.code;
30+
return [BAD_GATEWAY, SERVICE_UNAVAILABLE, TIMEOUT_STATUS].includes(statusCode)
31+
}
32+
33+
34+
async retry(uniqueKey, fn, ...args) {
35+
36+
if (!this.retryInfoMap.has(uniqueKey)) {
37+
this.retryInfoMap.set(uniqueKey, {
38+
fn: fn,
39+
args: args,
40+
retryCount: 0,
41+
retryTimer: null,
42+
isRetryInProgress: false
43+
})
44+
}
45+
46+
const retryInfo = this.retryInfoMap.get(uniqueKey);
47+
48+
retryInfo.isRetryInProgress = true;
49+
retryInfo.retryCount++;
50+
51+
await (new Promise((resolve, reject) => {
52+
retryInfo.retryTimer = setTimeout(resolve, this._getNextRetrySeconds(retryInfo.retryCount));
53+
}))
54+
55+
return await this._makeRetry(uniqueKey);
56+
}
57+
58+
59+
_getNextRetrySeconds(retryCount) {
60+
let nextRetrySeconds = 30 * 1000; // 30 seconds
61+
62+
if (retryCount > 3) {
63+
const MAX_MINUTES_TO_WAIT = 3;
64+
const MINUTES_TO_WAIT = Math.min((retryCount - 3), MAX_MINUTES_TO_WAIT);
65+
nextRetrySeconds = 1000 * 60 * MINUTES_TO_WAIT;
66+
}
67+
68+
return nextRetrySeconds;
69+
}
70+
71+
72+
73+
async _makeRetry(uniqueKey) {
74+
75+
const retryInfo = this.retryInfoMap.get(uniqueKey);
76+
77+
clearTimeout(retryInfo.retryTimer);
78+
79+
try {
80+
logger.debug(`Retrying api call for ${uniqueKey}. count: ${retryInfo.retryCount}`);
81+
retryInfo.isRetry = true;
82+
const data = await retryInfo.fn(...retryInfo.args);
83+
logger.debug(`api call succeeded. stopping retry for ${uniqueKey}`);
84+
this.resetRetryState(uniqueKey);
85+
return data;
86+
87+
} catch(error) {
88+
retryInfo.isRetry = false;
89+
logger.debug(`API call failed on retry ${retryInfo.retryCount}: ${error.message}`);
90+
return await this.retry(uniqueKey, retryInfo.fn, ...retryInfo.args);
91+
92+
}
93+
}
94+
95+
96+
resetRetryState(uniqueKey) {
97+
98+
const retryInfo = this.retryInfoMap.get(uniqueKey);
99+
100+
if (retryInfo.retryTimer) {
101+
clearTimeout(retryInfo.retryTimer);
102+
}
103+
104+
retryInfo.isRetry = false;
105+
retryInfo.isRetryInProgress = false;
106+
retryInfo.retryCount = 0;
107+
}
108+
109+
isRetryInProgress(uniqueKey) {
110+
return this.retryInfoMap.get(uniqueKey)? this.retryInfoMap.get(uniqueKey).isRetryInProgress: false;
111+
}
112+
}
113+
114+
module.exports = {
115+
RetryManger
116+
}

express/routes.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function setupRoutes(ext) {
2020
ext = ExtensionFactory.getExtension(cluster_id)
2121
}
2222
let companyId = parseInt(req.query.company_id);
23-
let platformConfig = ext.getPlatformConfig(companyId);
23+
let platformConfig = await ext.getPlatformConfig(companyId);
2424
let session;
2525
if (ext.isOnlineAccessMode()) {
2626
session = new Session(Session.generateSessionId(true));
@@ -107,7 +107,7 @@ function setupRoutes(ext) {
107107
}
108108
const companyId = req.fdkSession.company_id
109109

110-
const platformConfig = ext.getPlatformConfig(req.fdkSession.company_id);
110+
const platformConfig = await ext.getPlatformConfig(req.fdkSession.company_id);
111111
await platformConfig.oauthClient.verifyCallback(req.query);
112112

113113
let token = platformConfig.oauthClient.raw_token;
@@ -183,7 +183,7 @@ function setupRoutes(ext) {
183183
}
184184
logger.debug(`Extension auto install started for company: ${company_id} on company creation.`);
185185

186-
let platformConfig = ext.getPlatformConfig(company_id);
186+
let platformConfig = await ext.getPlatformConfig(company_id);
187187
let sid = Session.generateSessionId(false, {
188188
cluster: ext.cluster,
189189
companyId: company_id

0 commit comments

Comments
 (0)