@contrail/flexplm 1.7.7-alpha.d153eea → 1.7.7-alpha.e66ae52
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.
|
@@ -4,19 +4,6 @@ exports.FlexPLMConnect = void 0;
|
|
|
4
4
|
const app_framework_1 = require("@contrail/app-framework");
|
|
5
5
|
// Module-level on purpose: a new FlexPLMConnect is constructed for every event. Declaring for Session reusing use case.
|
|
6
6
|
const sessionCache = new Map();
|
|
7
|
-
// Upper bound so users/option combinations that are never requested again cannot accumulate cookies forever.
|
|
8
|
-
const MAX_CACHED_SESSIONS = 100;
|
|
9
|
-
const pruneSessionCache = () => {
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
for (const [key, session] of sessionCache) {
|
|
12
|
-
if (session.expiresAt <= now)
|
|
13
|
-
sessionCache.delete(key);
|
|
14
|
-
}
|
|
15
|
-
// Still full: drop the oldest entries (Map keeps insertion order).
|
|
16
|
-
while (sessionCache.size >= MAX_CACHED_SESSIONS) {
|
|
17
|
-
sessionCache.delete(sessionCache.keys().next().value);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
7
|
// In-flight cold session acquisitions per key, so concurrent events share one FlexPLM session instead of each creating their own.
|
|
21
8
|
const inFlightAcquisitions = new Map();
|
|
22
9
|
const DEFAULT_SESSION_COOKIE_NAME = 'JSESSIONID';
|
|
@@ -157,11 +144,7 @@ class FlexPLMConnect {
|
|
|
157
144
|
}
|
|
158
145
|
storeSession(cookie) {
|
|
159
146
|
// sliding expiry: Windchill's session timeout is also sliding
|
|
160
|
-
|
|
161
|
-
// delete first so a refreshed entry moves to the newest position (oldest-first eviction then acts as LRU)
|
|
162
|
-
sessionCache.delete(key);
|
|
163
|
-
pruneSessionCache();
|
|
164
|
-
sessionCache.set(key, { cookie, expiresAt: Date.now() + this.sessionOptions.ttlMs });
|
|
147
|
+
sessionCache.set(this.getSessionKey(), { cookie, expiresAt: Date.now() + this.sessionOptions.ttlMs });
|
|
165
148
|
}
|
|
166
149
|
clearSession() {
|
|
167
150
|
sessionCache.delete(this.getSessionKey());
|
|
@@ -243,7 +226,7 @@ class FlexPLMConnect {
|
|
|
243
226
|
await wait;
|
|
244
227
|
if (getNewSession) {
|
|
245
228
|
const current = this.getCachedSession();
|
|
246
|
-
if (
|
|
229
|
+
if (!current || current === rejectedCookie)
|
|
247
230
|
this.clearSession();
|
|
248
231
|
}
|
|
249
232
|
const cached = this.getCachedSession();
|
|
@@ -156,7 +156,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
156
156
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
157
157
|
expect(callHeaders(1).CSRF_NONCE).toBe('n1');
|
|
158
158
|
});
|
|
159
|
-
it('
|
|
159
|
+
it('reuses the cached session on the next event (new FlexPLMConnect instance)', async () => {
|
|
160
160
|
const u = uniqueUser();
|
|
161
161
|
fetchMock()
|
|
162
162
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/'])) // event 1 GET
|
|
@@ -168,7 +168,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
168
168
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1'); // GET reuses it
|
|
169
169
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=abc.tomcat1'); // POST reuses it
|
|
170
170
|
});
|
|
171
|
-
it('
|
|
171
|
+
it('replaces the cached session when Windchill answers the GET with a new Set-Cookie', async () => {
|
|
172
172
|
const u = uniqueUser();
|
|
173
173
|
fetchMock()
|
|
174
174
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/']))
|
|
@@ -185,7 +185,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
185
185
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1'); // POST uses the replacement
|
|
186
186
|
expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1'); // event 3 GET sends the replacement
|
|
187
187
|
});
|
|
188
|
-
it('
|
|
188
|
+
it('retries once with a new session when the POST is rejected with 403', async () => {
|
|
189
189
|
const u = uniqueUser();
|
|
190
190
|
fetchMock()
|
|
191
191
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/'])) // GET
|
|
@@ -199,7 +199,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
199
199
|
expect(result.status).toBe(200);
|
|
200
200
|
expect(result.data).toEqual({ ok: true });
|
|
201
201
|
});
|
|
202
|
-
it('
|
|
202
|
+
it('also retries on 401', async () => {
|
|
203
203
|
const u = uniqueUser();
|
|
204
204
|
fetchMock()
|
|
205
205
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
@@ -209,7 +209,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
209
209
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
|
|
210
210
|
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
211
211
|
});
|
|
212
|
-
it('
|
|
212
|
+
it('does not loop: a second 403 after the retry is surfaced as an error', async () => {
|
|
213
213
|
const u = uniqueUser();
|
|
214
214
|
fetchMock()
|
|
215
215
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
@@ -220,7 +220,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
220
220
|
.rejects.toThrow('Error sending data to FlexPLM:status: 403');
|
|
221
221
|
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
222
222
|
});
|
|
223
|
-
it('
|
|
223
|
+
it('does not retry on non-auth errors such as 500 (event may already be processed)', async () => {
|
|
224
224
|
const u = uniqueUser();
|
|
225
225
|
fetchMock()
|
|
226
226
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -229,7 +229,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
229
229
|
.rejects.toThrow('Error sending data to FlexPLM:status: 500');
|
|
230
230
|
expect(fetchMock()).toHaveBeenCalledTimes(2);
|
|
231
231
|
});
|
|
232
|
-
it('
|
|
232
|
+
it('retries the csrf GET without a cookie when the cached session is rejected there', async () => {
|
|
233
233
|
const u = uniqueUser();
|
|
234
234
|
fetchMock()
|
|
235
235
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1'])) // event 1 GET
|
|
@@ -243,7 +243,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
243
243
|
expect(callHeaders(3).Cookie).toBeUndefined();
|
|
244
244
|
expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1');
|
|
245
245
|
});
|
|
246
|
-
it('
|
|
246
|
+
it('expires the cached session after ttlSeconds', async () => {
|
|
247
247
|
const u = uniqueUser();
|
|
248
248
|
const realNow = Date.now();
|
|
249
249
|
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
@@ -260,7 +260,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
260
260
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=def.tomcat1');
|
|
261
261
|
nowSpy.mockRestore();
|
|
262
262
|
});
|
|
263
|
-
it('
|
|
263
|
+
it('uses the last JSESSIONID when several Set-Cookie lines are returned', async () => {
|
|
264
264
|
const u = uniqueUser();
|
|
265
265
|
fetchMock()
|
|
266
266
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=first.tomcat1; Path=/', 'OTHER=x; Path=/', 'JSESSIONID=second.tomcat1; Path=/']))
|
|
@@ -268,7 +268,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
268
268
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
|
|
269
269
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=second.tomcat1');
|
|
270
270
|
});
|
|
271
|
-
it('
|
|
271
|
+
it('falls back to headers.get("set-cookie") when getSetCookie is not available', async () => {
|
|
272
272
|
const u = uniqueUser();
|
|
273
273
|
const legacy = {
|
|
274
274
|
status: 200,
|
|
@@ -280,7 +280,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
280
280
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
|
|
281
281
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=legacy.tomcat1');
|
|
282
282
|
});
|
|
283
|
-
it('
|
|
283
|
+
it('honours a custom cookieName', async () => {
|
|
284
284
|
const u = uniqueUser();
|
|
285
285
|
fetchMock()
|
|
286
286
|
.mockResolvedValueOnce(resp(200, ['MYSESSION=zzz; Path=/', 'JSESSIONID=ignored; Path=/']))
|
|
@@ -288,7 +288,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
288
288
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'MYSESSION' })).sendToFlexPLM(payload);
|
|
289
289
|
expect(callHeaders(1).Cookie).toBe('MYSESSION=zzz');
|
|
290
290
|
});
|
|
291
|
-
it('
|
|
291
|
+
it('accepts enabled as the string "true"', async () => {
|
|
292
292
|
const u = uniqueUser();
|
|
293
293
|
fetchMock()
|
|
294
294
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -296,39 +296,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
296
296
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: 'true' })).sendToFlexPLM(payload);
|
|
297
297
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
298
298
|
});
|
|
299
|
-
it('
|
|
300
|
-
const userA = uniqueUser();
|
|
301
|
-
const userB = uniqueUser();
|
|
302
|
-
const realNow = Date.now();
|
|
303
|
-
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
304
|
-
fetchMock()
|
|
305
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=a.tomcat1'])) // A GET
|
|
306
|
-
.mockResolvedValueOnce(resp(200, [], {})) // A POST
|
|
307
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=b.tomcat1'])) // B GET
|
|
308
|
-
.mockResolvedValueOnce(resp(200, [], {})) // B POST
|
|
309
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=a2.tomcat1'])) // A GET again
|
|
310
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
311
|
-
const cfg = (u) => sessionConfig(u, { enabled: true, ttlSeconds: 1 });
|
|
312
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
|
|
313
|
-
nowSpy.mockReturnValue(realNow + 2000); // A has expired
|
|
314
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg(userB)).sendToFlexPLM(payload); // storing B sweeps A
|
|
315
|
-
nowSpy.mockReturnValue(realNow); // rewind: only a swept A has no entry left
|
|
316
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
|
|
317
|
-
expect(callHeaders(4).Cookie).toBeUndefined();
|
|
318
|
-
nowSpy.mockRestore();
|
|
319
|
-
});
|
|
320
|
-
it('should not hold more than the maximum number of cached sessions', async () => {
|
|
321
|
-
const first = uniqueUser();
|
|
322
|
-
fetchMock().mockImplementation(async () => resp(200, ['JSESSIONID=s.tomcat1']));
|
|
323
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
|
|
324
|
-
for (let i = 0; i < 100; i++) {
|
|
325
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(uniqueUser())).sendToFlexPLM(payload);
|
|
326
|
-
}
|
|
327
|
-
fetchMock().mockClear();
|
|
328
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
|
|
329
|
-
expect(callHeaders(0).Cookie).toBeUndefined(); // oldest entry was evicted
|
|
330
|
-
});
|
|
331
|
-
it('should keep separate sessions per user', async () => {
|
|
299
|
+
it('keeps separate sessions per user', async () => {
|
|
332
300
|
const a = uniqueUser();
|
|
333
301
|
const b = uniqueUser();
|
|
334
302
|
fetchMock()
|
|
@@ -341,7 +309,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
341
309
|
expect(callHeaders(2).Cookie).toBeUndefined(); // user b must not see user a's cookie
|
|
342
310
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=bbb.tomcat1');
|
|
343
311
|
});
|
|
344
|
-
it('
|
|
312
|
+
it('never logs the cookie value', async () => {
|
|
345
313
|
const isInfoOn = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
|
|
346
314
|
const info = jest.spyOn(console, 'info').mockImplementation();
|
|
347
315
|
const u = uniqueUser();
|
|
@@ -372,11 +340,11 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
372
340
|
['a comma separated string', '401, 403'],
|
|
373
341
|
['garbage (falls back to 401/403)', 'abc'],
|
|
374
342
|
['an empty array (falls back to 401/403)', []],
|
|
375
|
-
])('
|
|
343
|
+
])('retryStatuses given as %s does not throw and still retries on 403', async (_label, retryStatuses) => {
|
|
376
344
|
await rejectedOnceThenOk(uniqueUser(), { retryStatuses });
|
|
377
345
|
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
378
346
|
});
|
|
379
|
-
it.each([['abc'], [0], [-5]])('
|
|
347
|
+
it.each([['abc'], [0], [-5]])('invalid ttlSeconds (%p) falls back to the 900s default', async (ttlSeconds) => {
|
|
380
348
|
const u = uniqueUser();
|
|
381
349
|
const realNow = Date.now();
|
|
382
350
|
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
@@ -392,7 +360,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
392
360
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
393
361
|
nowSpy.mockRestore();
|
|
394
362
|
});
|
|
395
|
-
it('
|
|
363
|
+
it('a blank cookieName falls back to JSESSIONID', async () => {
|
|
396
364
|
const u = uniqueUser();
|
|
397
365
|
fetchMock()
|
|
398
366
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -505,7 +473,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
505
473
|
});
|
|
506
474
|
});
|
|
507
475
|
describe('cookie names containing regex metacharacters', () => {
|
|
508
|
-
it('
|
|
476
|
+
it('extracts a cookie whose name contains "+"', async () => {
|
|
509
477
|
const u = uniqueUser();
|
|
510
478
|
fetchMock()
|
|
511
479
|
.mockResolvedValueOnce(resp(200, ['SID+=abc; Path=/']))
|
|
@@ -513,7 +481,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
513
481
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'SID+' })).sendToFlexPLM(payload);
|
|
514
482
|
expect(callHeaders(1).Cookie).toBe('SID+=abc');
|
|
515
483
|
});
|
|
516
|
-
it('
|
|
484
|
+
it('treats "." literally and does not match a look-alike cookie', async () => {
|
|
517
485
|
const u = uniqueUser();
|
|
518
486
|
fetchMock()
|
|
519
487
|
.mockResolvedValueOnce(resp(200, ['AxB=wrong; Path=/']))
|
|
@@ -525,7 +493,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
525
493
|
describe('concurrent session acquisition', () => {
|
|
526
494
|
const flush = () => new Promise(r => setImmediate(r));
|
|
527
495
|
const isColdGet = (call) => call[1].method === 'GET' && !call[1].headers.Cookie;
|
|
528
|
-
it('
|
|
496
|
+
it('shares one session across concurrent events on a cold cache', async () => {
|
|
529
497
|
const u = uniqueUser();
|
|
530
498
|
let release;
|
|
531
499
|
const gate = new Promise(r => { release = r; });
|
|
@@ -544,7 +512,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
544
512
|
expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
|
|
545
513
|
results.forEach(r => expect(r.sessionCookie).toBe('abc.tomcat1'));
|
|
546
514
|
});
|
|
547
|
-
it('
|
|
515
|
+
it('a waiter does not inherit the failure of the first acquisition', async () => {
|
|
548
516
|
const u = uniqueUser();
|
|
549
517
|
let release;
|
|
550
518
|
const gate = new Promise(r => { release = r; });
|
|
@@ -559,7 +527,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
559
527
|
expect(await firstResult).toBeInstanceOf(Error);
|
|
560
528
|
expect((await second).sessionCookie).toBe('abc.tomcat1');
|
|
561
529
|
});
|
|
562
|
-
it('
|
|
530
|
+
it('parallel events rejected with 403 create only one replacement session', async () => {
|
|
563
531
|
const u = uniqueUser();
|
|
564
532
|
fetchMock().mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']));
|
|
565
533
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF(); // prime the cache with the old session
|
|
@@ -579,7 +547,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
579
547
|
});
|
|
580
548
|
describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
581
549
|
const payload = { id: '123' };
|
|
582
|
-
it('
|
|
550
|
+
it('sends no Cookie header and makes exactly one GET + one POST', async () => {
|
|
583
551
|
const u = uniqueUser();
|
|
584
552
|
fetchMock()
|
|
585
553
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // Set-Cookie present but must be ignored
|
|
@@ -593,7 +561,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
593
561
|
expect(callHeaders(i).Cookie).toBeUndefined();
|
|
594
562
|
}
|
|
595
563
|
});
|
|
596
|
-
it('
|
|
564
|
+
it('does not retry on 403 (behaves exactly as before)', async () => {
|
|
597
565
|
const u = uniqueUser();
|
|
598
566
|
fetchMock()
|
|
599
567
|
.mockResolvedValueOnce(resp(200))
|
|
@@ -602,7 +570,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
602
570
|
.rejects.toThrow('Error sending data to FlexPLM:status: 403');
|
|
603
571
|
expect(fetchMock()).toHaveBeenCalledTimes(2);
|
|
604
572
|
});
|
|
605
|
-
it('
|
|
573
|
+
it('explicit enabled:false behaves the same as not configured', async () => {
|
|
606
574
|
const u = uniqueUser();
|
|
607
575
|
fetchMock()
|
|
608
576
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -612,7 +580,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
612
580
|
});
|
|
613
581
|
});
|
|
614
582
|
describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
615
|
-
it('
|
|
583
|
+
it('sends the cached cookie on image/GET requests', async () => {
|
|
616
584
|
const u = uniqueUser();
|
|
617
585
|
fetchMock()
|
|
618
586
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // csrf GET primes the cache
|
|
@@ -623,7 +591,7 @@ describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
|
623
591
|
await connect.getRequest();
|
|
624
592
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
625
593
|
});
|
|
626
|
-
it('
|
|
594
|
+
it('retries once without the cookie when the cached session is rejected on getRequest', async () => {
|
|
627
595
|
const u = uniqueUser();
|
|
628
596
|
fetchMock()
|
|
629
597
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -636,7 +604,7 @@ describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
|
636
604
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
637
605
|
expect(callHeaders(3).Cookie).toBeUndefined();
|
|
638
606
|
});
|
|
639
|
-
it('
|
|
607
|
+
it('does not re-store the rejected cookie when the retry issues no Set-Cookie', async () => {
|
|
640
608
|
const u = uniqueUser();
|
|
641
609
|
fetchMock()
|
|
642
610
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=stale.tomcat1']))
|