Skip to content

Commit 2c4eb65

Browse files
authored
Merge branch 'main' into thumbnail-issues
2 parents 54de503 + d2170ee commit 2c4eb65

10 files changed

Lines changed: 316 additions & 80 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/arcgis-rest-basemap-sessions/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# @esri/arcgis-rest-basemap-sessions [1.1.0](https://github.com/Esri/arcgis-rest-js/compare/@esri/arcgis-rest-basemap-sessions@1.0.0...@esri/arcgis-rest-basemap-sessions@1.1.0) (2025-08-20)
2+
3+
4+
### Features
5+
6+
* **arcgis-rest-basemap-styles:** improve method names, edge cases and error handling ([d866803](https://github.com/Esri/arcgis-rest-js/commit/d8668032510f76d16d034f6e93d5347465d57e44))
7+
18
# @esri/arcgis-rest-basemap-sessions 1.0.0 (2025-08-11)
29

310

packages/arcgis-rest-basemap-sessions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@esri/arcgis-rest-basemap-sessions",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Helper functions for the Basemap Sessions service in @esri/arcgis-rest-js.",
55
"license": "Apache-2.0",
66
"keywords": [

packages/arcgis-rest-basemap-sessions/src/BaseSession.ts

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import mitt from "mitt";
22

33
import { IAuthenticationManager } from "@esri/arcgis-rest-request";
44
import { StyleFamily } from "./types/StyleFamily.js";
5-
import { startNewSession } from "./utils/startNewSession.js";
5+
import {
6+
IStartSessionResponse,
7+
startNewSession
8+
} from "./utils/startNewSession.js";
69
import { Writable } from "./utils/writable.js";
710
import { determineSafetyMargin } from "./utils/detemineSafetyMargin.js";
811
import {
912
DEFAULT_DURATION,
10-
DEFAULT_SAFETY_MARGIN,
1113
DEFAULT_CHECK_EXPIRATION_INTERVAL
1214
} from "./utils/defaults.js";
1315

@@ -56,7 +58,9 @@ export abstract class BaseSession implements IAuthenticationManager {
5658
// the static methods for event handlers are used to provide doc via typedoc and do not need to be tested.
5759
/* istanbul ignore next -- @preserve */
5860
/**
59-
* Event handler for when a session expires and the `token` it no longer valid.
61+
* Event handler for when the current session expires and the session token it no longer valid. This event will only fire if {@linkcode BaseSession.checkingExpirationTime}
62+
* is `true` which is the default. Once this event fires, {@linkcode BaseSession.checkingExpirationTime} will be set to `false` until the session is refreshed with
63+
* {@linkcode BaseSession.refreshCredentials}.
6064
*
6165
* @event expired
6266
* @param e - The parameters for the expired event.
@@ -159,13 +163,18 @@ export abstract class BaseSession implements IAuthenticationManager {
159163
/**
160164
* The interval at which to check the expiration time of the session. This is always 10 seconds or 1/100th of the duration, whichever is smaller.
161165
*/
162-
readonly expirationCheckInterval: number;
166+
private readonly expirationCheckInterval: number;
163167

164168
/**
165169
* The ID of the timer used to check the expiration time of the session.
166170
*/
167171
private expirationTimerId: any = null;
168172

173+
/**
174+
* A pending session that is being refreshed. This is used to prevent multiple refreshes from happening at the same time.
175+
*/
176+
private pendingSession: Promise<IStartSessionResponse> | null = null;
177+
169178
/**
170179
* Internal instance of [`mitt`](https://github.com/developit/mitt) used for event handlers. It is recommended to use {@linkcode BasemapSession.on}, {@linkcode BasemapSession.off} or {@linkcode BasemapSession.once} instead of `emitter.`
171180
*/
@@ -208,27 +217,30 @@ export abstract class BaseSession implements IAuthenticationManager {
208217
}
209218

210219
/**
211-
* Checks if the session is expired. If it is expired, it emits an "expired" event. The event will fire **before** the method returns true.
220+
* Checks if the session is expired. If it is expired, it emits an "expired" event and disables expiration time checking. The event will fire **before** the method returns true.
212221
*
213222
* @returns {boolean} - Returns true if the session is expired, otherwise false.
214223
*/
215224
isSessionExpired() {
216225
if (this.isExpired) {
226+
this.disableCheckingExpirationTime();
227+
217228
this.emitter.emit("expired", {
218229
token: this.token,
219230
startTime: this.startTime,
220231
endTime: this.endTime,
221232
expires: this.expires
222233
});
223234
}
235+
224236
return this.isExpired;
225237
}
226238

227239
/**
228240
* Starts checking the expiration time of the session. This will check the expiration time immediately and then on an interval.
229241
* If the session is expired, it will emit an "expired" event.
230242
*/
231-
startCheckingExpirationTime() {
243+
enableCheckingExpirationTime() {
232244
const check = () => {
233245
this.isSessionExpired();
234246
};
@@ -251,22 +263,13 @@ export abstract class BaseSession implements IAuthenticationManager {
251263
/**
252264
* Stops checking the expiration time of the session. This will clear the interval that was set by {@linkcode BaseSession.startCheckingExpirationTime}.
253265
*/
254-
stopCheckingExpirationTime() {
266+
disableCheckingExpirationTime() {
255267
if (this.expirationTimerId) {
256268
clearInterval(this.expirationTimerId);
257269
this.expirationTimerId = null;
258270
}
259271
}
260272

261-
/**
262-
* Indicates if the session is currently checking for expiration time.
263-
*
264-
* @returns {boolean} - Returns true if the session is checking for expiration time, otherwise false.
265-
*/
266-
get checkingExpirationTime(): boolean {
267-
return !!this.expirationTimerId;
268-
}
269-
270273
/**
271274
* Starts a new session using the provided parameters and returns an instance of the session class.
272275
*
@@ -281,7 +284,7 @@ export abstract class BaseSession implements IAuthenticationManager {
281284
authentication,
282285
safetyMargin,
283286
duration = DEFAULT_DURATION,
284-
autoRefresh = true
287+
autoRefresh = false
285288
}: {
286289
startSessionUrl?: string;
287290
styleFamily?: StyleFamily;
@@ -292,6 +295,16 @@ export abstract class BaseSession implements IAuthenticationManager {
292295
},
293296
SessionClass: new (params: IBasemapSessionParams) => T
294297
): Promise<T> {
298+
if (duration < 10) {
299+
throw new Error("Session duration must be at least 10 seconds.");
300+
}
301+
302+
if (duration > 43200) {
303+
throw new Error(
304+
"Session duration cannot exceed 12 hours (43200 seconds)."
305+
);
306+
}
307+
295308
const sessionResponse = await startNewSession({
296309
startSessionUrl,
297310
styleFamily,
@@ -312,15 +325,45 @@ export abstract class BaseSession implements IAuthenticationManager {
312325
duration
313326
});
314327

315-
session.startCheckingExpirationTime();
328+
session.enableCheckingExpirationTime();
316329

317330
if (autoRefresh) {
318-
session.startAutoRefresh();
331+
session.enableAutoRefresh();
319332
}
320333

321334
return session as T;
322335
}
323336

337+
/**
338+
* Indicates if the session is currently checking for expiration time.
339+
*
340+
* @returns {boolean} - Returns true if the session is checking for expiration time, otherwise false.
341+
*/
342+
get checkingExpirationTime(): boolean {
343+
return !!this.expirationTimerId;
344+
}
345+
346+
/**
347+
* Returns the number of seconds until the session is no longer valid rounded down. If the session is expired, it will return 0.
348+
*/
349+
get secondsUntilExpiration(): number {
350+
return Math.floor(this.millisecondsUntilExpiration / 1000);
351+
}
352+
353+
/**
354+
* Returns the number of milliseconds until the session token is no longer valid. If the session is expired, it will return 0.
355+
*/
356+
get millisecondsUntilExpiration(): number {
357+
if (this.isExpired) {
358+
return 0;
359+
}
360+
361+
const now = new Date();
362+
const millisecondsLeft = this.endTime.getTime() - now.getTime();
363+
364+
return millisecondsLeft;
365+
}
366+
324367
/**
325368
* Checks if the session is expired.
326369
*
@@ -343,7 +386,7 @@ export abstract class BaseSession implements IAuthenticationManager {
343386
}
344387

345388
/**
346-
* Indicates if the session can be refreshed. This is always true for this class.
389+
* Indicates if the session can be refreshed. This is always true for this basemap sessions.
347390
*
348391
* @returns {boolean} - Always returns true.
349392
*/
@@ -367,6 +410,12 @@ export abstract class BaseSession implements IAuthenticationManager {
367410
* @returns A promise that resolves to the current instance of the session.
368411
*/
369412
async refreshCredentials(): Promise<this> {
413+
if (this.pendingSession) {
414+
// if there is a pending session, wait for it to resolve
415+
await this.pendingSession;
416+
return this;
417+
}
418+
370419
// @TODO switch this to structured clone when we upgrade to Node 20+ types so we don't have to parse the dates later
371420
const previous = JSON.parse(
372421
JSON.stringify({
@@ -378,18 +427,24 @@ export abstract class BaseSession implements IAuthenticationManager {
378427
);
379428

380429
try {
381-
const newSession = await startNewSession({
430+
this.pendingSession = startNewSession({
382431
startSessionUrl: this.startSessionUrl,
383432
styleFamily: this.styleFamily,
384433
authentication: this.authentication,
385434
duration: this.duration
386435
});
387436

437+
const newSession = await this.pendingSession;
438+
439+
this.pendingSession = null; // reset the pending session
440+
388441
this.setToken(newSession.sessionToken);
389442
this.setStartTime(new Date(newSession.startTime));
390443
this.setEndTime(new Date(newSession.endTime));
391444
this.setExpires(new Date(newSession.endTime - this.safetyMargin * 1000));
392445

446+
this.enableCheckingExpirationTime(); // restart checking expiration time after refreshing credentials
447+
393448
this.emitter.emit("refreshed", {
394449
previous: {
395450
token: previous.token,
@@ -413,11 +468,11 @@ export abstract class BaseSession implements IAuthenticationManager {
413468
}
414469
/**
415470
* Enables auto-refresh for the session. This will automatically refresh the session when it expires.
416-
* It will also start checking the expiration time of the session if it is not already started via {@linkcode BaseSession.startCheckingExpirationTime}.
471+
* It will also start checking the expiration time of the session if it is not already started via {@linkcode BaseSession.enableCheckingExpirationTime}.
417472
*/
418-
startAutoRefresh() {
473+
enableAutoRefresh() {
419474
if (!this.expirationTimerId) {
420-
this.startCheckingExpirationTime();
475+
this.enableCheckingExpirationTime();
421476
}
422477

423478
this.autoRefreshHandler = () => {
@@ -432,15 +487,27 @@ export abstract class BaseSession implements IAuthenticationManager {
432487
/**
433488
* Disables auto-refresh for the session. This will stop automatically refreshing the session when it expires.
434489
* This will **not** stop checking the expiration time of the session. If you want to stop automated expiration
435-
* checking, call {@linkcode BaseSession.stopCheckingExpirationTime} after calling this method.
490+
* checking, call {@linkcode BaseSession.disableCheckingExpirationTime} after calling this method.
436491
*/
437-
stopAutoRefresh() {
492+
disableAutoRefresh() {
438493
if (this.autoRefreshHandler) {
439494
this.off("expired", this.autoRefreshHandler);
440495
this.autoRefreshHandler = null;
441496
}
442497
}
443498

499+
/**
500+
* Removes all event listeners and disables auto-refresh and expiration time checking. This is useful for cleaning up the session when it is no longer needed or replaced with a new session.
501+
*/
502+
destroy() {
503+
this.disableAutoRefresh();
504+
this.disableCheckingExpirationTime();
505+
this.emitter.off("expired");
506+
this.emitter.off("refreshed");
507+
this.emitter.off("error");
508+
this.emitter.off("*");
509+
}
510+
444511
/**
445512
* A handler that listens for an eventName and returns custom handler.
446513
*

0 commit comments

Comments
 (0)