@contrail/flexplm 1.7.7-alpha.99f62a7 → 1.7.7-alpha.c59ab5b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/interfaces/interfaces.d.ts +1 -13
- package/lib/publish/base-process-publish-assortment.js +4 -2
- package/lib/publish/base-process-publish-assortment.spec.js +106 -0
- package/lib/util/flexplm-connect.d.ts +4 -5
- package/lib/util/flexplm-connect.js +10 -133
- package/lib/util/flexplm-connect.spec.js +0 -617
- package/package.json +1 -1
- package/lib/util/flexplm-session-manager.d.ts +0 -40
- package/lib/util/flexplm-session-manager.js +0 -192
- package/lib/util/flexplm-session-manager.spec.d.ts +0 -1
- package/lib/util/flexplm-session-manager.spec.js +0 -70
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
export interface SessionReuseConfig {
|
|
2
|
-
enabled?: boolean | string;
|
|
3
|
-
ttlSeconds?: number | string;
|
|
4
|
-
cookieName?: string;
|
|
5
|
-
retryStatuses?: number | number[] | string;
|
|
6
|
-
}
|
|
7
|
-
export interface FlexPLMConnectConfig {
|
|
8
|
-
staticHeaders?: {
|
|
9
|
-
[key: string]: string;
|
|
10
|
-
};
|
|
11
|
-
sessionReuse?: SessionReuseConfig;
|
|
12
|
-
}
|
|
13
1
|
export interface FCConfig {
|
|
14
2
|
apiKey?: string;
|
|
15
3
|
orgSlug?: string;
|
|
@@ -17,7 +5,7 @@ export interface FCConfig {
|
|
|
17
5
|
configFile?: string;
|
|
18
6
|
eventObjectClassParam?: string;
|
|
19
7
|
eventFlexTypePathParam?: string;
|
|
20
|
-
flexplmConnect?:
|
|
8
|
+
flexplmConnect?: any;
|
|
21
9
|
taskId?: string;
|
|
22
10
|
apiHost: string;
|
|
23
11
|
userName(): string;
|
|
@@ -909,7 +909,8 @@ class BaseProcessPublishAssortment {
|
|
|
909
909
|
? itemFamilyChanges.assortmentItemFullChangeMap.get(entityReference)?.item
|
|
910
910
|
: itemFamilyChanges.itemFamilyObject;
|
|
911
911
|
let seasonalData = await this.getSeasonalData(projectItem);
|
|
912
|
-
|
|
912
|
+
const productSeasonMapKey = (await type_conversion_utils_1.TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, projectItem, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)) || 'LCSProductSeasonLink';
|
|
913
|
+
seasonalData = await map_utils_1.MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, productSeasonMapKey, 'vibe2flex');
|
|
913
914
|
const LCSProduct = await this.getProductFederation(entityReference, itemToFederatedIdMapping, prodEntityData);
|
|
914
915
|
const isCreatable = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(this.transformMapFile, this.mapFileUtil, projectItem, { item: prodEntityData, assortment });
|
|
915
916
|
const eventType = isCreatable ? 'UPSERT_ON_SEASON' : 'UPDATE_ON_SEASON';
|
|
@@ -944,7 +945,8 @@ class BaseProcessPublishAssortment {
|
|
|
944
945
|
}
|
|
945
946
|
const projectItem = this.getProjectItem(itemFamilyChanges, entityReference);
|
|
946
947
|
let seasonalData = await this.getSeasonalData(projectItem);
|
|
947
|
-
|
|
948
|
+
const skuSeasonMapKey = (await type_conversion_utils_1.TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, projectItem, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)) || 'LCSSKUSeasonLink';
|
|
949
|
+
seasonalData = await map_utils_1.MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, skuSeasonMapKey, 'vibe2flex');
|
|
948
950
|
const LCSSKU = await this.getSKUFederation(entityReference, itemToFederatedIdMapping, item);
|
|
949
951
|
const isCreatable = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(this.transformMapFile, this.mapFileUtil, projectItem, { item, assortment });
|
|
950
952
|
const eventType = isCreatable ? 'UPSERT_ON_SEASON' : 'UPDATE_ON_SEASON';
|
|
@@ -1509,6 +1509,9 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
|
1509
1509
|
};
|
|
1510
1510
|
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1511
1511
|
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1512
|
+
beforeEach(() => {
|
|
1513
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKey').mockResolvedValue(null);
|
|
1514
|
+
});
|
|
1512
1515
|
afterEach(() => {
|
|
1513
1516
|
jest.restoreAllMocks();
|
|
1514
1517
|
});
|
|
@@ -1705,6 +1708,108 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
|
1705
1708
|
// Verify SKU event called with assortment entity
|
|
1706
1709
|
expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
|
|
1707
1710
|
});
|
|
1711
|
+
it('should use dynamic map key for LCSSKUSeasonLink when getMapKey returns a custom key', async () => {
|
|
1712
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1713
|
+
const itemFamilyId = 'family123';
|
|
1714
|
+
const colorId = 'color456';
|
|
1715
|
+
const familyItemData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
|
|
1716
|
+
const familyProjectItem = { id: 'proj0', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1717
|
+
const colorProjectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1718
|
+
const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
|
|
1719
|
+
const seasonFed = { entityReference: 'assortment:assort123', objectClass: 'LCSSeason', flexPLMSeasonName: 'Fall 2023' };
|
|
1720
|
+
const seasonalData = { someData: 'value' };
|
|
1721
|
+
const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
|
|
1722
|
+
itemFamilyChanges.colorUpdates.push(colorId);
|
|
1723
|
+
itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, { item: familyItemData, projectItem: familyProjectItem });
|
|
1724
|
+
itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, { item: { id: colorId, identifier: 'COLOR-456' }, projectItem: colorProjectItem });
|
|
1725
|
+
jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
|
|
1726
|
+
jest.spyOn(bppa, 'getProjectItem')
|
|
1727
|
+
.mockReturnValueOnce(familyProjectItem)
|
|
1728
|
+
.mockReturnValueOnce(colorProjectItem);
|
|
1729
|
+
jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue(seasonalData);
|
|
1730
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
|
|
1731
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
|
|
1732
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1733
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
|
|
1734
|
+
// First call: product path (return null → falls back to default)
|
|
1735
|
+
// Second call: SKU path (return custom key)
|
|
1736
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKey')
|
|
1737
|
+
.mockResolvedValueOnce(null)
|
|
1738
|
+
.mockResolvedValueOnce('LCSSKUSeasonLinkFlexOwned');
|
|
1739
|
+
await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
|
|
1740
|
+
expect(map_utils_1.MapUtil.applyTransformMap).toHaveBeenCalledWith(undefined, mapFileUtil, seasonalData, 'LCSSKUSeasonLinkFlexOwned', 'vibe2flex');
|
|
1741
|
+
});
|
|
1742
|
+
it('should fall back to LCSSKUSeasonLink when getMapKey returns null', async () => {
|
|
1743
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1744
|
+
const itemFamilyId = 'family123';
|
|
1745
|
+
const colorId = 'color456';
|
|
1746
|
+
const familyItemData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
|
|
1747
|
+
const familyProjectItem = { id: 'proj0', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1748
|
+
const colorProjectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1749
|
+
const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
|
|
1750
|
+
const seasonFed = { entityReference: 'assortment:assort123', objectClass: 'LCSSeason', flexPLMSeasonName: 'Fall 2023' };
|
|
1751
|
+
const seasonalData = { someData: 'value' };
|
|
1752
|
+
const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
|
|
1753
|
+
itemFamilyChanges.colorUpdates.push(colorId);
|
|
1754
|
+
itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, { item: familyItemData, projectItem: familyProjectItem });
|
|
1755
|
+
itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, { item: { id: colorId, identifier: 'COLOR-456' }, projectItem: colorProjectItem });
|
|
1756
|
+
jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
|
|
1757
|
+
jest.spyOn(bppa, 'getProjectItem')
|
|
1758
|
+
.mockReturnValueOnce(familyProjectItem)
|
|
1759
|
+
.mockReturnValueOnce(colorProjectItem);
|
|
1760
|
+
jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue(seasonalData);
|
|
1761
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
|
|
1762
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
|
|
1763
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1764
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
|
|
1765
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKey').mockResolvedValue(null);
|
|
1766
|
+
await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
|
|
1767
|
+
expect(map_utils_1.MapUtil.applyTransformMap).toHaveBeenCalledWith(undefined, mapFileUtil, seasonalData, 'LCSSKUSeasonLink', 'vibe2flex');
|
|
1768
|
+
});
|
|
1769
|
+
it('should use dynamic map key for LCSProductSeasonLink when getMapKey returns a custom key', async () => {
|
|
1770
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1771
|
+
const itemFamilyId = 'family123';
|
|
1772
|
+
const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
|
|
1773
|
+
const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1774
|
+
const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
|
|
1775
|
+
const seasonFed = { entityReference: 'assortment:assort123', objectClass: 'LCSSeason', flexPLMSeasonName: 'Fall 2023' };
|
|
1776
|
+
const seasonalData = { someData: 'value' };
|
|
1777
|
+
const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
|
|
1778
|
+
itemFamilyChanges.familyUpdate = true;
|
|
1779
|
+
itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, { item: prodEntityData, projectItem });
|
|
1780
|
+
jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
|
|
1781
|
+
jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
|
|
1782
|
+
jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue(seasonalData);
|
|
1783
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
|
|
1784
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
|
|
1785
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1786
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
|
|
1787
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKey').mockResolvedValue('LCSProductSeasonLinkCustom');
|
|
1788
|
+
await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
|
|
1789
|
+
expect(map_utils_1.MapUtil.applyTransformMap).toHaveBeenCalledWith(undefined, mapFileUtil, seasonalData, 'LCSProductSeasonLinkCustom', 'vibe2flex');
|
|
1790
|
+
});
|
|
1791
|
+
it('should fall back to LCSProductSeasonLink when getMapKey returns null', async () => {
|
|
1792
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1793
|
+
const itemFamilyId = 'family123';
|
|
1794
|
+
const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
|
|
1795
|
+
const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1796
|
+
const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
|
|
1797
|
+
const seasonFed = { entityReference: 'assortment:assort123', objectClass: 'LCSSeason', flexPLMSeasonName: 'Fall 2023' };
|
|
1798
|
+
const seasonalData = { someData: 'value' };
|
|
1799
|
+
const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
|
|
1800
|
+
itemFamilyChanges.familyUpdate = true;
|
|
1801
|
+
itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, { item: prodEntityData, projectItem });
|
|
1802
|
+
jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
|
|
1803
|
+
jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
|
|
1804
|
+
jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue(seasonalData);
|
|
1805
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
|
|
1806
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
|
|
1807
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1808
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
|
|
1809
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKey').mockResolvedValue(null);
|
|
1810
|
+
await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
|
|
1811
|
+
expect(map_utils_1.MapUtil.applyTransformMap).toHaveBeenCalledWith(undefined, mapFileUtil, seasonalData, 'LCSProductSeasonLink', 'vibe2flex');
|
|
1812
|
+
});
|
|
1708
1813
|
});
|
|
1709
1814
|
describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
1710
1815
|
const config = {
|
|
@@ -1932,6 +2037,7 @@ describe('released family with pre-development options', () => {
|
|
|
1932
2037
|
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['itemNumber']);
|
|
1933
2038
|
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1934
2039
|
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(true);
|
|
2040
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKey').mockResolvedValue(null);
|
|
1935
2041
|
};
|
|
1936
2042
|
afterEach(() => { jest.restoreAllMocks(); });
|
|
1937
2043
|
/////////////////////////////////////////////////////////////////////////////
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { FCConfig, FlexPLMResponseData, PayloadType } from '../interfaces/interfaces';
|
|
2
2
|
export declare class FlexPLMConnect {
|
|
3
3
|
private config;
|
|
4
|
-
private sessions;
|
|
5
4
|
private vibeEventEndpoint;
|
|
6
5
|
private staticHeaders;
|
|
7
6
|
payloadSendAsArray: boolean;
|
|
8
7
|
constructor(_config: FCConfig, endPoint?: any, payloadAsArray?: any);
|
|
9
|
-
/** Releases the connection of a rejected response before it is replaced by a retry. */
|
|
10
|
-
private discardBody;
|
|
11
8
|
private getRequestOptions;
|
|
12
|
-
getCSRF(
|
|
13
|
-
|
|
9
|
+
getCSRF(): Promise<{
|
|
10
|
+
nonce_key: any;
|
|
11
|
+
nonce: any;
|
|
12
|
+
}>;
|
|
14
13
|
sendRequest(csrf: any, payload: any): Promise<Response>;
|
|
15
14
|
protected processRequest(payload: any): Promise<FlexPLMResponseData>;
|
|
16
15
|
sendToFlexPLM(payload: PayloadType): Promise<FlexPLMResponseData>;
|
|
@@ -2,23 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FlexPLMConnect = void 0;
|
|
4
4
|
const app_framework_1 = require("@contrail/app-framework");
|
|
5
|
-
const flexplm_session_manager_1 = require("./flexplm-session-manager");
|
|
6
|
-
// Cookie values carry the session id: mask them in logs whatever the header-name casing.
|
|
7
|
-
const maskCookieHeaders = (headers) => {
|
|
8
|
-
if (!headers)
|
|
9
|
-
return;
|
|
10
|
-
for (const key of Object.keys(headers)) {
|
|
11
|
-
if (key.toLowerCase() === 'cookie' && headers[key])
|
|
12
|
-
headers[key] = '***masked***';
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
5
|
class FlexPLMConnect {
|
|
16
6
|
constructor(_config, endPoint = undefined, payloadAsArray = undefined) {
|
|
17
7
|
this.vibeEventEndpoint = '';
|
|
18
8
|
this.staticHeaders = undefined;
|
|
19
9
|
this.payloadSendAsArray = true;
|
|
20
10
|
this.config = _config;
|
|
21
|
-
this.sessions = new flexplm_session_manager_1.FlexPLMSessionManager(_config);
|
|
22
11
|
this.vibeEventEndpoint = (endPoint)
|
|
23
12
|
? endPoint
|
|
24
13
|
: this.config.vibeEventEndpoint;
|
|
@@ -29,22 +18,10 @@ class FlexPLMConnect {
|
|
|
29
18
|
this.staticHeaders = this.config?.flexplmConnect?.staticHeaders;
|
|
30
19
|
}
|
|
31
20
|
}
|
|
32
|
-
// ET-390 - Session reuse
|
|
33
|
-
/** Releases the connection of a rejected response before it is replaced by a retry. */
|
|
34
|
-
async discardBody(response) {
|
|
35
|
-
try {
|
|
36
|
-
if (typeof response?.body?.cancel === 'function')
|
|
37
|
-
await response.body.cancel();
|
|
38
|
-
}
|
|
39
|
-
catch (e) {
|
|
40
|
-
// best effort: nothing to do if the body is already consumed or locked
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
21
|
/////////////////////////////////////////////////////////////////////////////
|
|
44
22
|
///////// Custom getRequestOptions: start
|
|
45
23
|
/////////////////////////////////////////////////////////////////////////////
|
|
46
|
-
|
|
47
|
-
getRequestOptions(method, sessionCookie) {
|
|
24
|
+
getRequestOptions(method) {
|
|
48
25
|
const csrfOptions = {
|
|
49
26
|
method,
|
|
50
27
|
headers: {
|
|
@@ -56,23 +33,9 @@ class FlexPLMConnect {
|
|
|
56
33
|
if (this.staticHeaders) {
|
|
57
34
|
Object.assign(csrfOptions.headers, this.staticHeaders);
|
|
58
35
|
}
|
|
59
|
-
// Added for Session reuse - ET-390 - Starts
|
|
60
|
-
// Merge with a configured static Cookie header (any header-name casing) instead of replacing it.
|
|
61
|
-
const headers = csrfOptions.headers;
|
|
62
|
-
const staticCookieKeys = Object.keys(headers).filter(key => key.toLowerCase() === 'cookie');
|
|
63
|
-
const staticCookie = staticCookieKeys.map(key => headers[key]).join('; ');
|
|
64
|
-
const cookie = this.sessions.buildCookieHeader(staticCookie, sessionCookie);
|
|
65
|
-
staticCookieKeys.forEach(key => delete headers[key]);
|
|
66
|
-
if (cookie) {
|
|
67
|
-
headers['Cookie'] = cookie;
|
|
68
|
-
}
|
|
69
|
-
// Added for Session reuse - ET-390 - Ends
|
|
70
36
|
if (app_framework_1.Logger.isInfoOn()) {
|
|
71
37
|
const logOptions = JSON.parse(JSON.stringify(csrfOptions));
|
|
72
38
|
logOptions['headers']['Authorization'] = logOptions?.headers?.Authorization.substring(0, 9);
|
|
73
|
-
// Added for Session reuse - ET-390 - Starts
|
|
74
|
-
maskCookieHeaders(logOptions.headers);
|
|
75
|
-
// Added for Session reuse - ET-390 - Ends
|
|
76
39
|
console.info('csrfOptions: ' + JSON.stringify(logOptions));
|
|
77
40
|
}
|
|
78
41
|
return csrfOptions;
|
|
@@ -80,52 +43,13 @@ class FlexPLMConnect {
|
|
|
80
43
|
/////////////////////////////////////////////////////////////////////////////
|
|
81
44
|
///////// Custom getRequestOptions: end
|
|
82
45
|
/////////////////////////////////////////////////////////////////////////////
|
|
83
|
-
|
|
84
|
-
// Whenever a getCSRF is called without getNewSession, the default value of false will be used
|
|
85
|
-
// rejectedCookie: the cookie that was rejected by the caller, so a refresh already done by a concurrent event is not thrown away
|
|
86
|
-
async getCSRF(getNewSession = false, rejectedCookie) {
|
|
87
|
-
// Added for Session reuse - ET-390 - Starts
|
|
88
|
-
if (!this.sessions.options.enabled)
|
|
89
|
-
return this.fetchCSRF(undefined);
|
|
90
|
-
for (let wait = this.sessions.waitForInFlightAcquisition(); wait; wait = this.sessions.waitForInFlightAcquisition())
|
|
91
|
-
await wait;
|
|
92
|
-
if (getNewSession) {
|
|
93
|
-
const current = this.sessions.getCachedSession();
|
|
94
|
-
if (rejectedCookie === undefined || !current || current === rejectedCookie)
|
|
95
|
-
this.sessions.clearSession();
|
|
96
|
-
}
|
|
97
|
-
const cached = this.sessions.getCachedSession();
|
|
98
|
-
if (cached) {
|
|
99
|
-
try {
|
|
100
|
-
return await this.fetchCSRF(cached);
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
if (!(e instanceof flexplm_session_manager_1.SessionRejectedError))
|
|
104
|
-
throw e;
|
|
105
|
-
// if the cached cookie is rejected (401/403), drop it and acquire a new session (serialized)
|
|
106
|
-
if (this.sessions.getCachedSession() === cached)
|
|
107
|
-
this.sessions.clearSession();
|
|
108
|
-
return this.getCSRF();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return this.sessions.runColdAcquisition(() => this.fetchCSRF(undefined));
|
|
112
|
-
// Added for Session reuse - ET-390 - Ends
|
|
113
|
-
}
|
|
114
|
-
async fetchCSRF(sentCookie) {
|
|
46
|
+
async getCSRF() {
|
|
115
47
|
const urlContext = this.config.urlContext;
|
|
116
48
|
const csrfEndpoint = this.config.csrfEndpoint;
|
|
117
49
|
const csrfURL = this.config.apiHost + urlContext + csrfEndpoint;
|
|
118
50
|
console.info('csrfURL: ' + csrfURL);
|
|
119
|
-
|
|
120
|
-
const csrfOptions = this.getRequestOptions('GET', sentCookie);
|
|
51
|
+
const csrfOptions = this.getRequestOptions('GET');
|
|
121
52
|
const response = await fetch(csrfURL, csrfOptions);
|
|
122
|
-
// Added for Session reuse - ET-390 - Starts
|
|
123
|
-
if (sentCookie && this.sessions.options.retryStatuses.includes(response.status)) {
|
|
124
|
-
console.warn('Cached FlexPLM session rejected on csrf call (status ' + response.status + '). Retrying with a new session.');
|
|
125
|
-
await this.discardBody(response);
|
|
126
|
-
throw new flexplm_session_manager_1.SessionRejectedError();
|
|
127
|
-
}
|
|
128
|
-
// Added for Session reuse - ET-390 - Ends
|
|
129
53
|
if (response.status >= 300) {
|
|
130
54
|
const message = 'Error connecting to FlexPLM:status: ' + response.status;
|
|
131
55
|
console.error(message);
|
|
@@ -134,24 +58,14 @@ class FlexPLMConnect {
|
|
|
134
58
|
e['httpResponseStatus'] = response.status;
|
|
135
59
|
throw e;
|
|
136
60
|
}
|
|
137
|
-
// Added for Session reuse - ET-390 - Starts
|
|
138
|
-
// If the cookie we sent was invalid, Windchill answers with a fresh Set-Cookie --> replace the cache.
|
|
139
|
-
let sessionCookie = sentCookie;
|
|
140
|
-
if (this.sessions.options.enabled) {
|
|
141
|
-
sessionCookie = this.sessions.extractSessionCookie(response) ?? sentCookie;
|
|
142
|
-
if (sessionCookie)
|
|
143
|
-
this.sessions.storeSession(sessionCookie, sentCookie);
|
|
144
|
-
}
|
|
145
|
-
// Added for Session reuse - ET-390 - Ends
|
|
146
61
|
try {
|
|
147
62
|
const body = await response.json();
|
|
148
63
|
const nonce_key = body.items[0].attributes.nonce_key;
|
|
149
64
|
const nonce = body.items[0].attributes.nonce;
|
|
150
|
-
|
|
65
|
+
console.info('nonce_key: ' + nonce_key);
|
|
151
66
|
return {
|
|
152
67
|
nonce_key,
|
|
153
|
-
nonce
|
|
154
|
-
sessionCookie,
|
|
68
|
+
nonce
|
|
155
69
|
};
|
|
156
70
|
}
|
|
157
71
|
catch (e) {
|
|
@@ -165,7 +79,7 @@ class FlexPLMConnect {
|
|
|
165
79
|
if (this.payloadSendAsArray && !Array.isArray(payload)) {
|
|
166
80
|
payload = [payload];
|
|
167
81
|
}
|
|
168
|
-
const xferOptions = this.getRequestOptions('POST'
|
|
82
|
+
const xferOptions = this.getRequestOptions('POST');
|
|
169
83
|
xferOptions['body'] = JSON.stringify(payload);
|
|
170
84
|
xferOptions.headers[csrf.nonce_key] = csrf.nonce;
|
|
171
85
|
const urlContext = this.config.urlContext;
|
|
@@ -176,7 +90,6 @@ class FlexPLMConnect {
|
|
|
176
90
|
const logOptions = JSON.parse(JSON.stringify(xferOptions));
|
|
177
91
|
if (logOptions?.headers?.Authorization)
|
|
178
92
|
logOptions.headers.Authorization = logOptions?.headers?.Authorization.substring(0, 9);
|
|
179
|
-
maskCookieHeaders(logOptions?.headers);
|
|
180
93
|
if (csrf?.nonce_key)
|
|
181
94
|
logOptions.headers[csrf.nonce_key] = csrf.nonce ? `***masked(length:${csrf.nonce.length})***` : '***masked(empty)***';
|
|
182
95
|
console.info('csrfOptions: ' + JSON.stringify(logOptions));
|
|
@@ -194,22 +107,13 @@ class FlexPLMConnect {
|
|
|
194
107
|
if (app_framework_1.Logger.isInfoOn()) {
|
|
195
108
|
console.info('payload: ' + JSON.stringify(payload));
|
|
196
109
|
}
|
|
197
|
-
|
|
110
|
+
const csrf = await this.getCSRF();
|
|
198
111
|
if (!csrf) {
|
|
199
112
|
const message = 'Failed to get CSRF nonce';
|
|
200
113
|
console.error(message);
|
|
201
114
|
throw new Error(message);
|
|
202
115
|
}
|
|
203
|
-
|
|
204
|
-
// const response = await this.sendRequest(csrf, payload);
|
|
205
|
-
let response = await this.sendRequest(csrf, payload);
|
|
206
|
-
if (csrf.sessionCookie && this.sessions.options.retryStatuses.includes(response.status)) {
|
|
207
|
-
console.warn('FlexPLM rejected session/nonce (status ' + response.status + '). Getting a new session and retrying once.');
|
|
208
|
-
await this.discardBody(response);
|
|
209
|
-
csrf = await this.getCSRF(true, csrf.sessionCookie);
|
|
210
|
-
response = await this.sendRequest(csrf, payload);
|
|
211
|
-
}
|
|
212
|
-
// Added for Session reuse - ET-390 - Ends
|
|
116
|
+
const response = await this.sendRequest(csrf, payload);
|
|
213
117
|
const status = response.status;
|
|
214
118
|
if (status >= 300) {
|
|
215
119
|
const message = 'Error sending data to FlexPLM:status: ' + response.status;
|
|
@@ -262,35 +166,8 @@ class FlexPLMConnect {
|
|
|
262
166
|
const urlContext = includeUrlContext ? this.config.urlContext : '';
|
|
263
167
|
const path = urlPath || ('/servlet/rest' + this.vibeEventEndpoint);
|
|
264
168
|
const vibeEventsURL = this.config.apiHost + urlContext + path;
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
if (!this.sessions.options.enabled) {
|
|
268
|
-
return { response: await fetch(vibeEventsURL, this.getRequestOptions('GET')), sent: undefined };
|
|
269
|
-
}
|
|
270
|
-
for (let wait = this.sessions.waitForInFlightAcquisition(); wait; wait = this.sessions.waitForInFlightAcquisition())
|
|
271
|
-
await wait;
|
|
272
|
-
const sent = this.sessions.getCachedSession();
|
|
273
|
-
const call = async () => {
|
|
274
|
-
const res = await fetch(vibeEventsURL, this.getRequestOptions('GET', sent));
|
|
275
|
-
// A rejected cookie must not be stored again; only keep a cookie that was accepted or newly issued.
|
|
276
|
-
if (!(sent && this.sessions.options.retryStatuses.includes(res.status))) {
|
|
277
|
-
const cookie = this.sessions.extractSessionCookie(res) ?? sent;
|
|
278
|
-
if (cookie)
|
|
279
|
-
this.sessions.storeSession(cookie, sent);
|
|
280
|
-
}
|
|
281
|
-
return { response: res, sent };
|
|
282
|
-
};
|
|
283
|
-
return sent ? call() : this.sessions.runColdAcquisition(call);
|
|
284
|
-
};
|
|
285
|
-
const first = await attempt();
|
|
286
|
-
let response = first.response;
|
|
287
|
-
if (first.sent && this.sessions.options.retryStatuses.includes(response.status)) {
|
|
288
|
-
await this.discardBody(response);
|
|
289
|
-
if (this.sessions.getCachedSession() === first.sent)
|
|
290
|
-
this.sessions.clearSession();
|
|
291
|
-
response = (await attempt()).response;
|
|
292
|
-
}
|
|
293
|
-
// Added for Session reuse - ET-390 - Ends
|
|
169
|
+
const csrfOptions = this.getRequestOptions('GET');
|
|
170
|
+
const response = await fetch(vibeEventsURL, csrfOptions);
|
|
294
171
|
if (response.status >= 300) {
|
|
295
172
|
const message = 'Error connecting to FlexPLM:status: ' + response.status;
|
|
296
173
|
console.error(message);
|