@contrail/flexplm 1.7.7-alpha.279b642 → 1.7.7-alpha.522cd89

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.
@@ -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?: FlexPLMConnectConfig;
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
- seasonalData = await map_utils_1.MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSProductSeasonLink', 'vibe2flex');
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
- seasonalData = await map_utils_1.MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSSKUSeasonLink', 'vibe2flex');
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(getNewSession?: boolean, rejectedCookie?: string): any;
13
- private fetchCSRF;
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,14 +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
5
  class FlexPLMConnect {
7
6
  constructor(_config, endPoint = undefined, payloadAsArray = undefined) {
8
7
  this.vibeEventEndpoint = '';
9
8
  this.staticHeaders = undefined;
10
9
  this.payloadSendAsArray = true;
11
10
  this.config = _config;
12
- this.sessions = new flexplm_session_manager_1.FlexPLMSessionManager(_config);
13
11
  this.vibeEventEndpoint = (endPoint)
14
12
  ? endPoint
15
13
  : this.config.vibeEventEndpoint;
@@ -20,22 +18,10 @@ class FlexPLMConnect {
20
18
  this.staticHeaders = this.config?.flexplmConnect?.staticHeaders;
21
19
  }
22
20
  }
23
- // ET-390 - Session reuse
24
- /** Releases the connection of a rejected response before it is replaced by a retry. */
25
- async discardBody(response) {
26
- try {
27
- if (typeof response?.body?.cancel === 'function')
28
- await response.body.cancel();
29
- }
30
- catch (e) {
31
- // best effort: nothing to do if the body is already consumed or locked
32
- }
33
- }
34
21
  /////////////////////////////////////////////////////////////////////////////
35
22
  ///////// Custom getRequestOptions: start
36
23
  /////////////////////////////////////////////////////////////////////////////
37
- // ET-390 - adding a new parameter sessionCookie to the getRequestOptions function
38
- getRequestOptions(method, sessionCookie) {
24
+ getRequestOptions(method) {
39
25
  const csrfOptions = {
40
26
  method,
41
27
  headers: {
@@ -47,19 +33,9 @@ class FlexPLMConnect {
47
33
  if (this.staticHeaders) {
48
34
  Object.assign(csrfOptions.headers, this.staticHeaders);
49
35
  }
50
- // Added for Session reuse - ET-390 - Starts
51
- const cookie = this.sessions.cookieHeader(sessionCookie);
52
- if (cookie) {
53
- csrfOptions.headers['Cookie'] = cookie;
54
- }
55
- // Added for Session reuse - ET-390 - Ends
56
36
  if (app_framework_1.Logger.isInfoOn()) {
57
37
  const logOptions = JSON.parse(JSON.stringify(csrfOptions));
58
38
  logOptions['headers']['Authorization'] = logOptions?.headers?.Authorization.substring(0, 9);
59
- // Added for Session reuse - ET-390 - Starts
60
- if (logOptions.headers['Cookie'])
61
- logOptions.headers['Cookie'] = '***masked***';
62
- // Added for Session reuse - ET-390 - Ends
63
39
  console.info('csrfOptions: ' + JSON.stringify(logOptions));
64
40
  }
65
41
  return csrfOptions;
@@ -67,52 +43,13 @@ class FlexPLMConnect {
67
43
  /////////////////////////////////////////////////////////////////////////////
68
44
  ///////// Custom getRequestOptions: end
69
45
  /////////////////////////////////////////////////////////////////////////////
70
- // ET-390 - adding a new parameter getNewSession to the getCSRF function
71
- // Whenever a getCSRF is called without getNewSession, the default value of false will be used
72
- // rejectedCookie: the cookie that was rejected by the caller, so a refresh already done by a concurrent event is not thrown away
73
- async getCSRF(getNewSession = false, rejectedCookie) {
74
- // Added for Session reuse - ET-390 - Starts
75
- if (!this.sessions.options.enabled)
76
- return this.fetchCSRF(undefined);
77
- for (let wait = this.sessions.waitForInFlightAcquisition(); wait; wait = this.sessions.waitForInFlightAcquisition())
78
- await wait;
79
- if (getNewSession) {
80
- const current = this.sessions.getCachedSession();
81
- if (rejectedCookie === undefined || !current || current === rejectedCookie)
82
- this.sessions.clearSession();
83
- }
84
- const cached = this.sessions.getCachedSession();
85
- if (cached) {
86
- try {
87
- return await this.fetchCSRF(cached);
88
- }
89
- catch (e) {
90
- if (!(e instanceof flexplm_session_manager_1.SessionRejectedError))
91
- throw e;
92
- // if the cached cookie is rejected (401/403), drop it and acquire a new session (serialized)
93
- if (this.sessions.getCachedSession() === cached)
94
- this.sessions.clearSession();
95
- return this.getCSRF();
96
- }
97
- }
98
- return this.sessions.runColdAcquisition(() => this.fetchCSRF(undefined));
99
- // Added for Session reuse - ET-390 - Ends
100
- }
101
- async fetchCSRF(sentCookie) {
46
+ async getCSRF() {
102
47
  const urlContext = this.config.urlContext;
103
48
  const csrfEndpoint = this.config.csrfEndpoint;
104
49
  const csrfURL = this.config.apiHost + urlContext + csrfEndpoint;
105
50
  console.info('csrfURL: ' + csrfURL);
106
- // getRequestOptions will be called with additional sentCookie parameter
107
- const csrfOptions = this.getRequestOptions('GET', sentCookie);
51
+ const csrfOptions = this.getRequestOptions('GET');
108
52
  const response = await fetch(csrfURL, csrfOptions);
109
- // Added for Session reuse - ET-390 - Starts
110
- if (sentCookie && this.sessions.options.retryStatuses.includes(response.status)) {
111
- console.warn('Cached FlexPLM session rejected on csrf call (status ' + response.status + '). Retrying with a new session.');
112
- await this.discardBody(response);
113
- throw new flexplm_session_manager_1.SessionRejectedError();
114
- }
115
- // Added for Session reuse - ET-390 - Ends
116
53
  if (response.status >= 300) {
117
54
  const message = 'Error connecting to FlexPLM:status: ' + response.status;
118
55
  console.error(message);
@@ -121,24 +58,14 @@ class FlexPLMConnect {
121
58
  e['httpResponseStatus'] = response.status;
122
59
  throw e;
123
60
  }
124
- // Added for Session reuse - ET-390 - Starts
125
- // If the cookie we sent was invalid, Windchill answers with a fresh Set-Cookie --> replace the cache.
126
- let sessionCookie = sentCookie;
127
- if (this.sessions.options.enabled) {
128
- sessionCookie = this.sessions.extractSessionCookie(response) ?? sentCookie;
129
- if (sessionCookie)
130
- this.sessions.storeSession(sessionCookie);
131
- }
132
- // Added for Session reuse - ET-390 - Ends
133
61
  try {
134
62
  const body = await response.json();
135
63
  const nonce_key = body.items[0].attributes.nonce_key;
136
64
  const nonce = body.items[0].attributes.nonce;
137
- // ET-390 - adding sessionCookie to the return
65
+ console.info('nonce_key: ' + nonce_key);
138
66
  return {
139
67
  nonce_key,
140
- nonce,
141
- sessionCookie,
68
+ nonce
142
69
  };
143
70
  }
144
71
  catch (e) {
@@ -152,7 +79,7 @@ class FlexPLMConnect {
152
79
  if (this.payloadSendAsArray && !Array.isArray(payload)) {
153
80
  payload = [payload];
154
81
  }
155
- const xferOptions = this.getRequestOptions('POST', csrf?.sessionCookie);
82
+ const xferOptions = this.getRequestOptions('POST');
156
83
  xferOptions['body'] = JSON.stringify(payload);
157
84
  xferOptions.headers[csrf.nonce_key] = csrf.nonce;
158
85
  const urlContext = this.config.urlContext;
@@ -163,8 +90,6 @@ class FlexPLMConnect {
163
90
  const logOptions = JSON.parse(JSON.stringify(xferOptions));
164
91
  if (logOptions?.headers?.Authorization)
165
92
  logOptions.headers.Authorization = logOptions?.headers?.Authorization.substring(0, 9);
166
- if (logOptions?.headers?.Cookie)
167
- logOptions.headers.Cookie = '***masked***';
168
93
  if (csrf?.nonce_key)
169
94
  logOptions.headers[csrf.nonce_key] = csrf.nonce ? `***masked(length:${csrf.nonce.length})***` : '***masked(empty)***';
170
95
  console.info('csrfOptions: ' + JSON.stringify(logOptions));
@@ -182,22 +107,13 @@ class FlexPLMConnect {
182
107
  if (app_framework_1.Logger.isInfoOn()) {
183
108
  console.info('payload: ' + JSON.stringify(payload));
184
109
  }
185
- let csrf = await this.getCSRF();
110
+ const csrf = await this.getCSRF();
186
111
  if (!csrf) {
187
112
  const message = 'Failed to get CSRF nonce';
188
113
  console.error(message);
189
114
  throw new Error(message);
190
115
  }
191
- /// Added for Session reuse - ET-390 - Starts
192
- // const response = await this.sendRequest(csrf, payload);
193
- let response = await this.sendRequest(csrf, payload);
194
- if (csrf.sessionCookie && this.sessions.options.retryStatuses.includes(response.status)) {
195
- console.warn('FlexPLM rejected session/nonce (status ' + response.status + '). Getting a new session and retrying once.');
196
- await this.discardBody(response);
197
- csrf = await this.getCSRF(true, csrf.sessionCookie);
198
- response = await this.sendRequest(csrf, payload);
199
- }
200
- // Added for Session reuse - ET-390 - Ends
116
+ const response = await this.sendRequest(csrf, payload);
201
117
  const status = response.status;
202
118
  if (status >= 300) {
203
119
  const message = 'Error sending data to FlexPLM:status: ' + response.status;
@@ -250,35 +166,8 @@ class FlexPLMConnect {
250
166
  const urlContext = includeUrlContext ? this.config.urlContext : '';
251
167
  const path = urlPath || ('/servlet/rest' + this.vibeEventEndpoint);
252
168
  const vibeEventsURL = this.config.apiHost + urlContext + path;
253
- // Added for Session reuse - ET-390 - Starts
254
- const attempt = async () => {
255
- if (!this.sessions.options.enabled) {
256
- return { response: await fetch(vibeEventsURL, this.getRequestOptions('GET')), sent: undefined };
257
- }
258
- for (let wait = this.sessions.waitForInFlightAcquisition(); wait; wait = this.sessions.waitForInFlightAcquisition())
259
- await wait;
260
- const sent = this.sessions.getCachedSession();
261
- const call = async () => {
262
- const res = await fetch(vibeEventsURL, this.getRequestOptions('GET', sent));
263
- // A rejected cookie must not be stored again; only keep a cookie that was accepted or newly issued.
264
- if (!(sent && this.sessions.options.retryStatuses.includes(res.status))) {
265
- const cookie = this.sessions.extractSessionCookie(res) ?? sent;
266
- if (cookie)
267
- this.sessions.storeSession(cookie);
268
- }
269
- return { response: res, sent };
270
- };
271
- return sent ? call() : this.sessions.runColdAcquisition(call);
272
- };
273
- const first = await attempt();
274
- let response = first.response;
275
- if (first.sent && this.sessions.options.retryStatuses.includes(response.status)) {
276
- await this.discardBody(response);
277
- if (this.sessions.getCachedSession() === first.sent)
278
- this.sessions.clearSession();
279
- response = (await attempt()).response;
280
- }
281
- // Added for Session reuse - ET-390 - Ends
169
+ const csrfOptions = this.getRequestOptions('GET');
170
+ const response = await fetch(vibeEventsURL, csrfOptions);
282
171
  if (response.status >= 300) {
283
172
  const message = 'Error connecting to FlexPLM:status: ' + response.status;
284
173
  console.error(message);
@@ -28,23 +28,6 @@ const createConfig = (overrides) => ({
28
28
  useDistinctRestEndPointForImages: false,
29
29
  ...overrides,
30
30
  });
31
- // Added for Session reuse - ET-390 - Starts
32
- const csrfBody = { items: [{ attributes: { nonce_key: 'CSRF_NONCE', nonce: 'n1' } }] };
33
- // Response with Set-Cookie support. setCookie = list of raw Set-Cookie header lines.
34
- const resp = (status, setCookie = [], json = csrfBody) => ({
35
- status,
36
- json: jest.fn().mockResolvedValue(json),
37
- text: jest.fn().mockResolvedValue('error text'),
38
- headers: { getSetCookie: () => setCookie, get: () => null },
39
- });
40
- let userCounter = 0;
41
- const uniqueUser = () => 'sess-user-' + (++userCounter);
42
- // Session options are nested: { flexplmConnect: { sessionReuse: { enabled, ... } } }
43
- const sessionConfig = (user, sessionReuse = { enabled: true }) => createConfig({ userName: () => user, flexplmConnect: { sessionReuse } });
44
- const fetchMock = () => global.fetch;
45
- const callHeaders = (i) => fetchMock().mock.calls[i][1].headers;
46
- const callMethod = (i) => fetchMock().mock.calls[i][1].method;
47
- // Added for Session reuse - ET-390 - Ends
48
31
  describe('FlexPLMConnect.getRequest', () => {
49
32
  it('should call default URL with urlContext when no params provided', async () => {
50
33
  const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
@@ -138,514 +121,3 @@ describe('FlexPLMConnect.sendRequest', () => {
138
121
  consoleInfoSpy.mockRestore();
139
122
  });
140
123
  });
141
- // Added for Session reuse - ET-390 - Starts
142
- describe('FlexPLMConnect session reuse', () => {
143
- const payload = { id: '123' };
144
- it('should capture JSESSIONID from the CSRF GET and send it on the POST', async () => {
145
- const u = uniqueUser();
146
- fetchMock()
147
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/; HttpOnly']))
148
- .mockResolvedValueOnce(resp(200, [], {}));
149
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
150
- expect(fetchMock()).toHaveBeenCalledTimes(2);
151
- expect(callHeaders(0).Cookie).toBeUndefined(); // first ever call: no cached session
152
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
153
- expect(callHeaders(1).CSRF_NONCE).toBe('n1');
154
- });
155
- it('should reuse the cached session on the next event (new FlexPLMConnect instance)', async () => {
156
- const u = uniqueUser();
157
- fetchMock()
158
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/'])) // event 1 GET
159
- .mockResolvedValueOnce(resp(200, [], {})) // event 1 POST
160
- .mockResolvedValueOnce(resp(200, [])) // event 2 GET (valid session -> no Set-Cookie)
161
- .mockResolvedValueOnce(resp(200, [], {})); // event 2 POST
162
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
163
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
164
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1'); // GET reuses it
165
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=abc.tomcat1'); // POST reuses it
166
- });
167
- it('should replace the cached session when Windchill answers the GET with a new Set-Cookie', async () => {
168
- const u = uniqueUser();
169
- fetchMock()
170
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/']))
171
- .mockResolvedValueOnce(resp(200, [], {}))
172
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/'])) // old one was stale -> new issued
173
- .mockResolvedValueOnce(resp(200, [], {}))
174
- .mockResolvedValueOnce(resp(200, []))
175
- .mockResolvedValueOnce(resp(200, [], {}));
176
- const connect = () => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
177
- await connect().sendToFlexPLM(payload);
178
- await connect().sendToFlexPLM(payload);
179
- await connect().sendToFlexPLM(payload);
180
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=old.tomcat1'); // event 2 GET sent the old one
181
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1'); // POST uses the replacement
182
- expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1'); // event 3 GET sends the replacement
183
- });
184
- it('should retry once with a new session when the POST is rejected with 403', async () => {
185
- const u = uniqueUser();
186
- fetchMock()
187
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/'])) // GET
188
- .mockResolvedValueOnce(resp(403, [], {})) // POST rejected
189
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/'])) // GET again, fresh session
190
- .mockResolvedValueOnce(resp(200, [], { ok: true })); // POST succeeds
191
- const result = await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
192
- expect(fetchMock()).toHaveBeenCalledTimes(4);
193
- expect(callHeaders(2).Cookie).toBeUndefined(); // retry GET must NOT send the stale cookie
194
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1');
195
- expect(result.status).toBe(200);
196
- expect(result.data).toEqual({ ok: true });
197
- });
198
- it('should also retry on 401', async () => {
199
- const u = uniqueUser();
200
- fetchMock()
201
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
202
- .mockResolvedValueOnce(resp(401, [], {}))
203
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
204
- .mockResolvedValueOnce(resp(200, [], {}));
205
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
206
- expect(fetchMock()).toHaveBeenCalledTimes(4);
207
- });
208
- it('should not loop: a second 403 after the retry is surfaced as an error', async () => {
209
- const u = uniqueUser();
210
- fetchMock()
211
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
212
- .mockResolvedValueOnce(resp(403, [], {}))
213
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
214
- .mockResolvedValueOnce(resp(403, [], {}));
215
- await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload))
216
- .rejects.toThrow('Error sending data to FlexPLM:status: 403');
217
- expect(fetchMock()).toHaveBeenCalledTimes(4);
218
- });
219
- it('should not retry on non-auth errors such as 500 (event may already be processed)', async () => {
220
- const u = uniqueUser();
221
- fetchMock()
222
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
223
- .mockResolvedValueOnce(resp(500, [], {}));
224
- await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload))
225
- .rejects.toThrow('Error sending data to FlexPLM:status: 500');
226
- expect(fetchMock()).toHaveBeenCalledTimes(2);
227
- });
228
- it('should retry the csrf GET without a cookie when the cached session is rejected there', async () => {
229
- const u = uniqueUser();
230
- fetchMock()
231
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1'])) // event 1 GET
232
- .mockResolvedValueOnce(resp(200, [], {})) // event 1 POST
233
- .mockResolvedValueOnce(resp(401, [])) // event 2 GET with cached cookie rejected
234
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1'])) // event 2 GET retry, no cookie
235
- .mockResolvedValueOnce(resp(200, [], {})); // event 2 POST
236
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
237
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
238
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=old.tomcat1');
239
- expect(callHeaders(3).Cookie).toBeUndefined();
240
- expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1');
241
- });
242
- it('should expire the cached session after ttlSeconds', async () => {
243
- const u = uniqueUser();
244
- const realNow = Date.now();
245
- const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
246
- fetchMock()
247
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
248
- .mockResolvedValueOnce(resp(200, [], {}))
249
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=def.tomcat1']))
250
- .mockResolvedValueOnce(resp(200, [], {}));
251
- const cfg = sessionConfig(u, { enabled: true, ttlSeconds: 1 });
252
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
253
- nowSpy.mockReturnValue(realNow + 2000); // 2s later > 1s ttl
254
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
255
- expect(callHeaders(2).Cookie).toBeUndefined(); // expired -> GET sends no cookie
256
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=def.tomcat1');
257
- nowSpy.mockRestore();
258
- });
259
- it('should use the last JSESSIONID when several Set-Cookie lines are returned', async () => {
260
- const u = uniqueUser();
261
- fetchMock()
262
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=first.tomcat1; Path=/', 'OTHER=x; Path=/', 'JSESSIONID=second.tomcat1; Path=/']))
263
- .mockResolvedValueOnce(resp(200, [], {}));
264
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
265
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=second.tomcat1');
266
- });
267
- it('should fall back to headers.get("set-cookie") when getSetCookie is not available', async () => {
268
- const u = uniqueUser();
269
- const legacy = {
270
- status: 200,
271
- json: jest.fn().mockResolvedValue(csrfBody),
272
- text: jest.fn(),
273
- headers: { get: (k) => (k.toLowerCase() === 'set-cookie' ? 'JSESSIONID=legacy.tomcat1; Path=/' : null) },
274
- };
275
- fetchMock().mockResolvedValueOnce(legacy).mockResolvedValueOnce(resp(200, [], {}));
276
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
277
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=legacy.tomcat1');
278
- });
279
- it('should honour a custom cookieName', async () => {
280
- const u = uniqueUser();
281
- fetchMock()
282
- .mockResolvedValueOnce(resp(200, ['MYSESSION=zzz; Path=/', 'JSESSIONID=ignored; Path=/']))
283
- .mockResolvedValueOnce(resp(200, [], {}));
284
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'MYSESSION' })).sendToFlexPLM(payload);
285
- expect(callHeaders(1).Cookie).toBe('MYSESSION=zzz');
286
- });
287
- it('should accept enabled as the string "true"', async () => {
288
- const u = uniqueUser();
289
- fetchMock()
290
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
291
- .mockResolvedValueOnce(resp(200, [], {}));
292
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: 'true' })).sendToFlexPLM(payload);
293
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
294
- });
295
- it('should evict expired sessions of other users when a new session is stored', async () => {
296
- const userA = uniqueUser();
297
- const userB = uniqueUser();
298
- const realNow = Date.now();
299
- const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
300
- fetchMock()
301
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=a.tomcat1'])) // A GET
302
- .mockResolvedValueOnce(resp(200, [], {})) // A POST
303
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=b.tomcat1'])) // B GET
304
- .mockResolvedValueOnce(resp(200, [], {})) // B POST
305
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=a2.tomcat1'])) // A GET again
306
- .mockResolvedValueOnce(resp(200, [], {}));
307
- const cfg = (u) => sessionConfig(u, { enabled: true, ttlSeconds: 1 });
308
- await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
309
- nowSpy.mockReturnValue(realNow + 2000); // A has expired
310
- await new flexplm_connect_1.FlexPLMConnect(cfg(userB)).sendToFlexPLM(payload); // storing B sweeps A
311
- nowSpy.mockReturnValue(realNow); // rewind: only a swept A has no entry left
312
- await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
313
- expect(callHeaders(4).Cookie).toBeUndefined();
314
- nowSpy.mockRestore();
315
- });
316
- it('should not hold more than the maximum number of cached sessions', async () => {
317
- const first = uniqueUser();
318
- fetchMock().mockImplementation(async () => resp(200, ['JSESSIONID=s.tomcat1']));
319
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
320
- for (let i = 0; i < 100; i++) {
321
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(uniqueUser())).sendToFlexPLM(payload);
322
- }
323
- fetchMock().mockClear();
324
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
325
- expect(callHeaders(0).Cookie).toBeUndefined(); // oldest entry was evicted
326
- });
327
- it('should keep separate sessions per user', async () => {
328
- const a = uniqueUser();
329
- const b = uniqueUser();
330
- fetchMock()
331
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=aaa.tomcat1']))
332
- .mockResolvedValueOnce(resp(200, [], {}))
333
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=bbb.tomcat1']))
334
- .mockResolvedValueOnce(resp(200, [], {}));
335
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(a)).sendToFlexPLM(payload);
336
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(b)).sendToFlexPLM(payload);
337
- expect(callHeaders(2).Cookie).toBeUndefined(); // user b must not see user a's cookie
338
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=bbb.tomcat1');
339
- });
340
- it('should never log the cookie value', async () => {
341
- const isInfoOn = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
342
- const info = jest.spyOn(console, 'info').mockImplementation();
343
- const u = uniqueUser();
344
- fetchMock()
345
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=supersecret.tomcat1']))
346
- .mockResolvedValueOnce(resp(200, [], {}))
347
- .mockResolvedValueOnce(resp(200, []))
348
- .mockResolvedValueOnce(resp(200, [], {}));
349
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
350
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload); // 2nd event sends Cookie on GET and POST
351
- const logged = info.mock.calls.map(c => String(c[0] ?? '')).join('\n');
352
- expect(logged).toContain('"Cookie":"***masked***"');
353
- expect(logged).not.toContain('supersecret');
354
- isInfoOn.mockRestore();
355
- info.mockRestore();
356
- });
357
- describe('option normalisation', () => {
358
- const rejectedOnceThenOk = (u, opts) => {
359
- fetchMock()
360
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
361
- .mockResolvedValueOnce(resp(403, [], {}))
362
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
363
- .mockResolvedValueOnce(resp(200, [], {}));
364
- return new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ...opts })).sendToFlexPLM(payload);
365
- };
366
- it.each([
367
- ['a scalar number', 403],
368
- ['a comma separated string', '401, 403'],
369
- ['garbage (falls back to 401/403)', 'abc'],
370
- ['an empty array (falls back to 401/403)', []],
371
- ])('should not throw and still retry on 403 when retryStatuses is %s', async (_label, retryStatuses) => {
372
- await rejectedOnceThenOk(uniqueUser(), { retryStatuses });
373
- expect(fetchMock()).toHaveBeenCalledTimes(4);
374
- });
375
- it.each([['abc'], [0], [-5]])('should fall back to the 900s default when ttlSeconds is invalid (%p)', async (ttlSeconds) => {
376
- const u = uniqueUser();
377
- const realNow = Date.now();
378
- const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
379
- fetchMock()
380
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
381
- .mockResolvedValueOnce(resp(200, [], {}))
382
- .mockResolvedValueOnce(resp(200, []))
383
- .mockResolvedValueOnce(resp(200, [], {}));
384
- const cfg = sessionConfig(u, { enabled: true, ttlSeconds });
385
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
386
- nowSpy.mockReturnValue(realNow + 2000);
387
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
388
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
389
- nowSpy.mockRestore();
390
- });
391
- it('should fall back to JSESSIONID when cookieName is blank', async () => {
392
- const u = uniqueUser();
393
- fetchMock()
394
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
395
- .mockResolvedValueOnce(resp(200, [], {}));
396
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: ' ' })).sendToFlexPLM(payload);
397
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
398
- });
399
- });
400
- describe('retryStatuses restricted to client-error statuses', () => {
401
- const postFailsWith = async (status, retryStatuses) => {
402
- const u = uniqueUser();
403
- fetchMock()
404
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
405
- .mockResolvedValueOnce(resp(status, [], {}));
406
- await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, retryStatuses })).sendToFlexPLM({ id: '1' }))
407
- .rejects.toThrow('Error sending data to FlexPLM:status: ' + status);
408
- expect(fetchMock()).toHaveBeenCalledTimes(2); // no retry
409
- };
410
- it.each([[500], ['500'], [[200, 500]]])('should ignore non-4xx retryStatuses (%p) and not retry a 500', async (retryStatuses) => {
411
- await postFailsWith(500, retryStatuses);
412
- });
413
- it('should keep 4xx entries and drop others from a mixed retryStatuses list', async () => {
414
- await postFailsWith(500, [401, 500]);
415
- });
416
- it('should not resend a successful event when retryStatuses is configured as 200', async () => {
417
- const u = uniqueUser();
418
- fetchMock()
419
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
420
- .mockResolvedValueOnce(resp(200, [], {}));
421
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, retryStatuses: 200 })).sendToFlexPLM({ id: '1' });
422
- expect(fetchMock()).toHaveBeenCalledTimes(2);
423
- });
424
- });
425
- describe('session cache key includes session options', () => {
426
- it('should not share a cached cookie between different cookieNames for the same user', async () => {
427
- const u = uniqueUser();
428
- fetchMock()
429
- .mockResolvedValueOnce(resp(200, ['AAA=a1; Path=/']))
430
- .mockResolvedValueOnce(resp(200, [], {}))
431
- .mockResolvedValueOnce(resp(200, ['BBB=b1; Path=/']))
432
- .mockResolvedValueOnce(resp(200, [], {}));
433
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'AAA' })).sendToFlexPLM({ id: '1' });
434
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'BBB' })).sendToFlexPLM({ id: '1' });
435
- expect(callHeaders(2).Cookie).toBeUndefined(); // BBB config must not pick up the AAA session
436
- expect(callHeaders(3).Cookie).toBe('BBB=b1');
437
- });
438
- it('should not share a cached session between different ttlSeconds for the same user', async () => {
439
- const u = uniqueUser();
440
- fetchMock()
441
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=short.tomcat1']))
442
- .mockResolvedValueOnce(resp(200, [], {}))
443
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=long.tomcat1']))
444
- .mockResolvedValueOnce(resp(200, [], {}));
445
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ttlSeconds: 1 })).sendToFlexPLM({ id: '1' });
446
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ttlSeconds: 900 })).sendToFlexPLM({ id: '1' });
447
- expect(callHeaders(2).Cookie).toBeUndefined();
448
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=long.tomcat1');
449
- });
450
- });
451
- describe('rejected response disposal', () => {
452
- const withBody = (r) => Object.assign(r, { body: { cancel: jest.fn().mockResolvedValue(undefined) } });
453
- it('should cancel the body of a POST rejected with 403 before retrying', async () => {
454
- const u = uniqueUser();
455
- const rejected = withBody(resp(403, [], {}));
456
- fetchMock()
457
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
458
- .mockResolvedValueOnce(rejected)
459
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
460
- .mockResolvedValueOnce(resp(200, [], {}));
461
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
462
- expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
463
- });
464
- it('should cancel the body of a CSRF GET rejected with the cached cookie before retrying', async () => {
465
- const u = uniqueUser();
466
- const rejected = withBody(resp(401, []));
467
- fetchMock()
468
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
469
- .mockResolvedValueOnce(resp(200, [], {}))
470
- .mockResolvedValueOnce(rejected)
471
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
472
- .mockResolvedValueOnce(resp(200, [], {}));
473
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
474
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
475
- expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
476
- });
477
- it('should cancel the body of a getRequest rejected with the cached cookie before retrying', async () => {
478
- const u = uniqueUser();
479
- const rejected = withBody(resp(401, []));
480
- fetchMock()
481
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
482
- .mockResolvedValueOnce(resp(200, [], {}))
483
- .mockResolvedValueOnce(rejected)
484
- .mockResolvedValueOnce(resp(200, [], { items: [] }));
485
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
486
- await connect.sendToFlexPLM({ id: '1' });
487
- await connect.getRequest();
488
- expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
489
- });
490
- it('should still retry when cancelling the body throws', async () => {
491
- const u = uniqueUser();
492
- const rejected = resp(403, [], {});
493
- rejected.body = { cancel: jest.fn().mockRejectedValue(new Error('locked')) };
494
- fetchMock()
495
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
496
- .mockResolvedValueOnce(rejected)
497
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
498
- .mockResolvedValueOnce(resp(200, [], {}));
499
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
500
- expect(fetchMock()).toHaveBeenCalledTimes(4);
501
- });
502
- });
503
- describe('cookie names containing regex metacharacters', () => {
504
- it('should extract a cookie whose name contains "+"', async () => {
505
- const u = uniqueUser();
506
- fetchMock()
507
- .mockResolvedValueOnce(resp(200, ['SID+=abc; Path=/']))
508
- .mockResolvedValueOnce(resp(200, [], {}));
509
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'SID+' })).sendToFlexPLM(payload);
510
- expect(callHeaders(1).Cookie).toBe('SID+=abc');
511
- });
512
- it('should treat "." literally and not match a look-alike cookie', async () => {
513
- const u = uniqueUser();
514
- fetchMock()
515
- .mockResolvedValueOnce(resp(200, ['AxB=wrong; Path=/']))
516
- .mockResolvedValueOnce(resp(200, [], {}));
517
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'A.B' })).sendToFlexPLM(payload);
518
- expect(callHeaders(1).Cookie).toBeUndefined();
519
- });
520
- });
521
- describe('concurrent session acquisition', () => {
522
- const flush = () => new Promise(r => setImmediate(r));
523
- const isColdGet = (call) => call[1].method === 'GET' && !call[1].headers.Cookie;
524
- it('should share one session across concurrent events on a cold cache', async () => {
525
- const u = uniqueUser();
526
- let release;
527
- const gate = new Promise(r => { release = r; });
528
- fetchMock().mockImplementation(async (_url, opts) => {
529
- if (opts.method === 'GET' && !opts.headers.Cookie) {
530
- await gate;
531
- return resp(200, ['JSESSIONID=abc.tomcat1']);
532
- }
533
- return resp(200, []);
534
- });
535
- const all = Promise.all([1, 2, 3].map(() => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF()));
536
- await flush();
537
- expect(fetchMock()).toHaveBeenCalledTimes(1); // the other two are waiting for the first acquisition
538
- release();
539
- const results = await all;
540
- expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
541
- results.forEach(r => expect(r.sessionCookie).toBe('abc.tomcat1'));
542
- });
543
- it('should not make a waiter inherit the failure of the first acquisition', async () => {
544
- const u = uniqueUser();
545
- let release;
546
- const gate = new Promise(r => { release = r; });
547
- fetchMock()
548
- .mockImplementationOnce(async () => { await gate; return resp(500, []); })
549
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']));
550
- const first = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF();
551
- const firstResult = first.catch(e => e);
552
- const second = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF();
553
- await flush();
554
- release();
555
- expect(await firstResult).toBeInstanceOf(Error);
556
- expect((await second).sessionCookie).toBe('abc.tomcat1');
557
- });
558
- it('should create only one replacement session for parallel events rejected with 403', async () => {
559
- const u = uniqueUser();
560
- fetchMock().mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']));
561
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF(); // prime the cache with the old session
562
- fetchMock().mockReset();
563
- fetchMock().mockImplementation(async (_url, opts) => {
564
- const cookie = opts.headers.Cookie;
565
- if (opts.method === 'POST')
566
- return cookie === 'JSESSIONID=new.tomcat1' ? resp(200, [], {}) : resp(403, [], {});
567
- if (!cookie)
568
- return resp(200, ['JSESSIONID=new.tomcat1']);
569
- return resp(200, []);
570
- });
571
- await Promise.all([1, 2, 3].map(() => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload)));
572
- expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
573
- });
574
- });
575
- });
576
- describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
577
- const payload = { id: '123' };
578
- it('should send no Cookie header and make exactly one GET + one POST', async () => {
579
- const u = uniqueUser();
580
- fetchMock()
581
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // Set-Cookie present but must be ignored
582
- .mockResolvedValueOnce(resp(200, [], {}))
583
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=def.tomcat1']))
584
- .mockResolvedValueOnce(resp(200, [], {}));
585
- const cfg = createConfig({ userName: () => u }); // no flexplmConnect at all
586
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
587
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
588
- for (let i = 0; i < 4; i++) {
589
- expect(callHeaders(i).Cookie).toBeUndefined();
590
- }
591
- });
592
- it('should not retry on 403 (behaves exactly as before)', async () => {
593
- const u = uniqueUser();
594
- fetchMock()
595
- .mockResolvedValueOnce(resp(200))
596
- .mockResolvedValueOnce(resp(403, [], {}));
597
- await expect(new flexplm_connect_1.FlexPLMConnect(createConfig({ userName: () => u })).sendToFlexPLM(payload))
598
- .rejects.toThrow('Error sending data to FlexPLM:status: 403');
599
- expect(fetchMock()).toHaveBeenCalledTimes(2);
600
- });
601
- it('should behave the same as not configured when enabled is false', async () => {
602
- const u = uniqueUser();
603
- fetchMock()
604
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
605
- .mockResolvedValueOnce(resp(200, [], {}));
606
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: false })).sendToFlexPLM(payload);
607
- expect(callHeaders(1).Cookie).toBeUndefined();
608
- });
609
- });
610
- describe('FlexPLMConnect.getRequest session reuse', () => {
611
- it('should send the cached cookie on image/GET requests', async () => {
612
- const u = uniqueUser();
613
- fetchMock()
614
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // csrf GET primes the cache
615
- .mockResolvedValueOnce(resp(200, [], {})) // POST
616
- .mockResolvedValueOnce(resp(200, [], { items: [] })); // getRequest
617
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
618
- await connect.sendToFlexPLM({ id: '1' });
619
- await connect.getRequest();
620
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
621
- });
622
- it('should retry once without the cookie when the cached session is rejected on getRequest', async () => {
623
- const u = uniqueUser();
624
- fetchMock()
625
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
626
- .mockResolvedValueOnce(resp(200, [], {}))
627
- .mockResolvedValueOnce(resp(401, [])) // getRequest with cached cookie
628
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1'], { items: [] })); // retry, no cookie
629
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
630
- await connect.sendToFlexPLM({ id: '1' });
631
- await connect.getRequest();
632
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
633
- expect(callHeaders(3).Cookie).toBeUndefined();
634
- });
635
- it('should not re-store the rejected cookie when the retry issues no Set-Cookie', async () => {
636
- const u = uniqueUser();
637
- fetchMock()
638
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=stale.tomcat1']))
639
- .mockResolvedValueOnce(resp(200, [], {}))
640
- .mockResolvedValueOnce(resp(401, [])) // getRequest with the cached cookie
641
- .mockResolvedValueOnce(resp(200, [], { items: [] })) // retry succeeds, no Set-Cookie
642
- .mockResolvedValueOnce(resp(200, [], { items: [] })); // next getRequest
643
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
644
- await connect.sendToFlexPLM({ id: '1' });
645
- await connect.getRequest();
646
- await connect.getRequest();
647
- expect(callHeaders(3).Cookie).toBeUndefined();
648
- expect(callHeaders(4).Cookie).toBeUndefined(); // stale cookie must not be sent again
649
- });
650
- });
651
- // Added for Session reuse - ET-390 - Ends
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.7.7-alpha.279b642",
3
+ "version": "1.7.7-alpha.522cd89",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,29 +0,0 @@
1
- import { FCConfig } from '../interfaces/interfaces';
2
- export declare class SessionRejectedError extends Error {
3
- }
4
- /** Owns the FlexPLM session cookie cache: options, keying, expiry, in-flight acquisition sharing and Set-Cookie parsing. */
5
- export declare class FlexPLMSessionManager {
6
- private config;
7
- constructor(config: FCConfig);
8
- get options(): {
9
- enabled: boolean;
10
- ttlMs: number;
11
- cookieName: string;
12
- retryStatuses: number[];
13
- };
14
- /**
15
- * Waits until no cold session acquisition is running for this key.
16
- * Callers must `await` this only when it returns a promise: awaiting even a resolved promise yields,
17
- * and the check-then-register in runColdAcquisition has to stay synchronous to be race free.
18
- */
19
- waitForInFlightAcquisition(): Promise<void> | undefined;
20
- /** Runs a cold acquisition and registers it so concurrent callers wait for it instead of opening their own session. */
21
- runColdAcquisition<T>(acquire: () => Promise<T>): Promise<T>;
22
- private getSessionKey;
23
- getCachedSession(): string | undefined;
24
- storeSession(cookie: string): void;
25
- clearSession(): void;
26
- /** Returns the session cookie value Windchill issued on this response, if any (last one wins). */
27
- extractSessionCookie(response: Response): string | undefined;
28
- cookieHeader(sessionCookie?: string): string | undefined;
29
- }
@@ -1,157 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FlexPLMSessionManager = exports.SessionRejectedError = void 0;
4
- // Module-level on purpose: a new FlexPLMConnect (and session manager) is constructed for every event.
5
- const sessionCache = new Map();
6
- // Upper bound so users/option combinations that are never requested again cannot accumulate cookies forever.
7
- const MAX_CACHED_SESSIONS = 100;
8
- const pruneSessionCache = () => {
9
- const now = Date.now();
10
- for (const [key, session] of sessionCache) {
11
- if (session.expiresAt <= now)
12
- sessionCache.delete(key);
13
- }
14
- // Still full: drop the oldest entries (Map keeps insertion order).
15
- while (sessionCache.size >= MAX_CACHED_SESSIONS) {
16
- sessionCache.delete(sessionCache.keys().next().value);
17
- }
18
- };
19
- // In-flight cold session acquisitions per key, so concurrent events share one FlexPLM session instead of each creating their own.
20
- const inFlightAcquisitions = new Map();
21
- const DEFAULT_SESSION_COOKIE_NAME = 'JSESSIONID';
22
- const DEFAULT_SESSION_TTL_SECONDS = 900;
23
- const DEFAULT_RETRY_STATUSES = [401, 403];
24
- const COOKIE_NAME_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
25
- // Thrown internally when a cached cookie was rejected, so the caller can re-acquire under the lock.
26
- class SessionRejectedError extends Error {
27
- }
28
- exports.SessionRejectedError = SessionRejectedError;
29
- const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
- const parseRetryStatuses = (value) => {
31
- let list = [];
32
- if (Array.isArray(value))
33
- list = value;
34
- else if (typeof value === 'string')
35
- list = value.split(',');
36
- else if (value !== undefined && value !== null)
37
- list = [value];
38
- const statuses = list
39
- .map(status => (typeof status === 'string' ? status.trim() : status))
40
- .filter(status => status !== '' && status !== null && status !== undefined)
41
- .map(Number)
42
- // Only client-error statuses can mean "session rejected". 2xx/5xx would resend events the server already processed.
43
- .filter(status => Number.isInteger(status) && status >= 400 && status <= 499);
44
- return statuses.length ? statuses : DEFAULT_RETRY_STATUSES;
45
- };
46
- const parseTtlSeconds = (value) => {
47
- const ttl = Number(value);
48
- return Number.isFinite(ttl) && ttl > 0 ? ttl : DEFAULT_SESSION_TTL_SECONDS;
49
- };
50
- const parseCookieName = (value) => {
51
- const name = typeof value === 'string' ? value.trim() : '';
52
- return COOKIE_NAME_TOKEN.test(name) ? name : DEFAULT_SESSION_COOKIE_NAME;
53
- };
54
- /** Owns the FlexPLM session cookie cache: options, keying, expiry, in-flight acquisition sharing and Set-Cookie parsing. */
55
- class FlexPLMSessionManager {
56
- constructor(config) {
57
- this.config = config;
58
- }
59
- get options() {
60
- const sessionReuseConfig = this.config?.flexplmConnect?.sessionReuse ?? {};
61
- return {
62
- enabled: sessionReuseConfig.enabled === true || String(sessionReuseConfig.enabled).toLowerCase() === 'true',
63
- ttlMs: parseTtlSeconds(sessionReuseConfig.ttlSeconds) * 1000,
64
- cookieName: parseCookieName(sessionReuseConfig.cookieName),
65
- retryStatuses: parseRetryStatuses(sessionReuseConfig.retryStatuses),
66
- };
67
- }
68
- /**
69
- * Waits until no cold session acquisition is running for this key.
70
- * Callers must `await` this only when it returns a promise: awaiting even a resolved promise yields,
71
- * and the check-then-register in runColdAcquisition has to stay synchronous to be race free.
72
- */
73
- waitForInFlightAcquisition() {
74
- const key = this.getSessionKey();
75
- if (!inFlightAcquisitions.has(key))
76
- return undefined;
77
- return (async () => {
78
- let pending = inFlightAcquisitions.get(key);
79
- while (pending) {
80
- await pending.catch(() => undefined);
81
- pending = inFlightAcquisitions.get(key);
82
- }
83
- })();
84
- }
85
- /** Runs a cold acquisition and registers it so concurrent callers wait for it instead of opening their own session. */
86
- async runColdAcquisition(acquire) {
87
- const key = this.getSessionKey();
88
- const acquisition = acquire();
89
- inFlightAcquisitions.set(key, acquisition);
90
- try {
91
- return await acquisition;
92
- }
93
- finally {
94
- if (inFlightAcquisitions.get(key) === acquisition)
95
- inFlightAcquisitions.delete(key);
96
- }
97
- }
98
- getSessionKey() {
99
- // Options that change the cached value are part of the key; JSON avoids '|' collisions between fields.
100
- const { cookieName, ttlMs } = this.options;
101
- return JSON.stringify([
102
- this.config.apiHost, this.config.urlContext, this.config.userName(), this.config.plmEnviornment, cookieName, ttlMs,
103
- ]);
104
- }
105
- getCachedSession() {
106
- if (!this.options.enabled)
107
- return undefined;
108
- const key = this.getSessionKey();
109
- const cachedSessValue = sessionCache.get(key);
110
- if (!cachedSessValue)
111
- return undefined;
112
- if (cachedSessValue.expiresAt <= Date.now()) {
113
- sessionCache.delete(key);
114
- return undefined;
115
- }
116
- return cachedSessValue.cookie;
117
- }
118
- storeSession(cookie) {
119
- // sliding expiry: Windchill's session timeout is also sliding
120
- const key = this.getSessionKey();
121
- // delete first so a refreshed entry moves to the newest position (oldest-first eviction then acts as LRU)
122
- sessionCache.delete(key);
123
- pruneSessionCache();
124
- sessionCache.set(key, { cookie, expiresAt: Date.now() + this.options.ttlMs });
125
- }
126
- clearSession() {
127
- sessionCache.delete(this.getSessionKey());
128
- }
129
- /** Returns the session cookie value Windchill issued on this response, if any (last one wins). */
130
- extractSessionCookie(response) {
131
- const respHeader = response?.headers;
132
- if (!respHeader)
133
- return undefined;
134
- let raw = [];
135
- if (typeof respHeader.getSetCookie === 'function') {
136
- raw = respHeader.getSetCookie();
137
- }
138
- else if (typeof respHeader.get === 'function' && respHeader.get('set-cookie')) {
139
- raw = [respHeader.get('set-cookie')];
140
- }
141
- const name = this.options.cookieName;
142
- const regexFilter = new RegExp('(?:^|[,\\s])' + escapeRegExp(name) + '=([^;,\\s]+)', 'g');
143
- let jSession;
144
- for (const line of raw) {
145
- let cookieNameMatcher = regexFilter.exec(line);
146
- while (cookieNameMatcher !== null) {
147
- jSession = cookieNameMatcher[1];
148
- cookieNameMatcher = regexFilter.exec(line);
149
- }
150
- }
151
- return jSession;
152
- }
153
- cookieHeader(sessionCookie) {
154
- return sessionCookie ? `${this.options.cookieName}=${sessionCookie}` : undefined;
155
- }
156
- }
157
- exports.FlexPLMSessionManager = FlexPLMSessionManager;