Skip to content

Commit ec63c02

Browse files
committed
feat(arcgis-rest-basemap-sessions): add basemap style sessions package
1 parent 5568e7c commit ec63c02

23 files changed

+165
-3501
lines changed

.vscode/settings.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
{
2-
"editor.tabSize": 2,
3-
"javascript.preferences.importModuleSpecifierEnding": "js",
4-
"typescript.preferences.importModuleSpecifierEnding": "js"
5-
}
2+
"editor.tabSize": 2,
3+
"javascript.preferences.importModuleSpecifierEnding": "js",
4+
"typescript.preferences.importModuleSpecifierEnding": "js",
5+
"editor.defaultFormatter": "esbenp.prettier-vscode",
6+
"[javascript]": {
7+
"editor.defaultFormatter": "esbenp.prettier-vscode"
8+
},
9+
"[typescript]": {
10+
"editor.defaultFormatter": "esbenp.prettier-vscode"
11+
}
12+
}

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

Lines changed: 137 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export interface IBasemapSessionParams {
1515
startSessionUrl: string;
1616
styleFamily: StyleFamily;
1717
authentication: IAuthenticationManager | string;
18-
expires: Date | string;
19-
startTime: Date | string;
20-
endTime: Date | string;
18+
expires: Date;
19+
startTime: Date;
20+
endTime: Date;
2121
safetyMargin?: number;
2222
duration?: number;
2323
}
@@ -26,7 +26,14 @@ export interface IStartSessionParams {
2626
styleFamily?: StyleFamily;
2727
authentication: IAuthenticationManager | string;
2828
saftyMargin?: number;
29-
testSession?: boolean;
29+
duration?: number;
30+
autoRefresh?: boolean;
31+
32+
/**
33+
* The URL to start the session. If not provided, it will use the default URL.
34+
* @private
35+
*/
36+
startSessionUrl?: string;
3037
}
3138

3239
/**
@@ -37,6 +44,15 @@ export interface IStartSessionParams {
3744
* @implements {IAuthenticationManager}
3845
*/
3946
export abstract class BaseSession implements IAuthenticationManager {
47+
// the static methods for event handlers are used to provide doc via typedoc and do not need to be tested.
48+
/* istanbul ignore next -- @preserve */
49+
/**
50+
* Event handler for when an error occurs during session management.
51+
*/
52+
static readonly error = function error(e: Error): void {}; // eslint-disable-line @typescript-eslint/no-empty-function
53+
54+
// the static methods for event handlers are used to provide doc via typedoc and do not need to be tested.
55+
/* istanbul ignore next -- @preserve */
4056
/**
4157
* Event handler for when a session expires and the `token` it no longer valid.
4258
*
@@ -54,6 +70,8 @@ export abstract class BaseSession implements IAuthenticationManager {
5470
expires: Date;
5571
}): void {}; // eslint-disable-line @typescript-eslint/no-empty-function
5672

73+
// the static methods for event handlers are used to provide doc via typedoc and do not need to be tested.
74+
/* istanbul ignore next -- @preserve */
5775
/**
5876
* Event handler for when a session refreshes and a new `token` is available.
5977
*
@@ -146,6 +164,11 @@ export abstract class BaseSession implements IAuthenticationManager {
146164
*/
147165
private emitter: any;
148166

167+
/**
168+
* A handler that is used to automatically refresh the session when it expires.
169+
*/
170+
private autoRefreshHandler: (() => void) | null = null;
171+
149172
/**
150173
* Creates a new instance of the BaseSession class. Generally you should not create an instance of this class directly, but instead use the static methods to start a session or deserialize a session.
151174
*
@@ -168,21 +191,11 @@ export abstract class BaseSession implements IAuthenticationManager {
168191
this.styleFamily = params.styleFamily || "arcgis";
169192
this.authentication = params.authentication;
170193
this.duration = params.duration || DEFAULT_DURATION;
171-
this.startTime =
172-
typeof params.startTime === "string"
173-
? new Date(params.startTime)
174-
: params.startTime;
175-
this.endTime =
176-
typeof params.endTime === "string"
177-
? new Date(params.endTime)
178-
: params.endTime;
179-
this.expires =
180-
typeof params.expires === "string"
181-
? new Date(params.expires)
182-
: params.expires;
194+
this.startTime = params.startTime;
195+
this.endTime = params.endTime;
196+
this.expires = params.expires;
183197
this.saftyMargin = params.safetyMargin || DEFAULT_SAFETY_MARGIN;
184198
this.emitter = mitt();
185-
this.startCheckingExpirationTime();
186199
}
187200

188201
/**
@@ -208,9 +221,6 @@ export abstract class BaseSession implements IAuthenticationManager {
208221
*/
209222
startCheckingExpirationTime() {
210223
const check = () => {
211-
console.log(
212-
"BaremapStyleSession.startCheckingExpirationTime(): Checking session expiration time..."
213-
);
214224
this.isSessionExpired();
215225
};
216226

@@ -224,6 +234,8 @@ export abstract class BaseSession implements IAuthenticationManager {
224234
setTimeout(() => {
225235
check(); // check immediately after starting the interval
226236
}, 10);
237+
238+
return this.expirationTimerId; // return the timer ID so it can be stopped later
227239
}
228240

229241
/**
@@ -236,6 +248,15 @@ export abstract class BaseSession implements IAuthenticationManager {
236248
}
237249
}
238250

251+
/**
252+
* Indicates if the session is currently checking for expiration time.
253+
*
254+
* @returns {boolean} - Returns true if the session is checking for expiration time, otherwise false.
255+
*/
256+
get checkingExpirationTime(): boolean {
257+
return !!this.expirationTimerId;
258+
}
259+
239260
/**
240261
* Starts a new session using the provided parameters and returns an instance of the session class.
241262
*
@@ -249,13 +270,15 @@ export abstract class BaseSession implements IAuthenticationManager {
249270
styleFamily = "arcgis",
250271
authentication,
251272
safetyMargin = DEFAULT_SAFETY_MARGIN,
252-
duration = DEFAULT_DURATION
273+
duration = DEFAULT_DURATION,
274+
autoRefresh = true
253275
}: {
254276
startSessionUrl?: string;
255277
styleFamily?: StyleFamily;
256278
authentication: IAuthenticationManager | string;
257279
safetyMargin?: number;
258280
duration?: number;
281+
autoRefresh?: boolean;
259282
},
260283
SessionClass: new (params: IBasemapSessionParams) => T
261284
): Promise<T> {
@@ -266,20 +289,24 @@ export abstract class BaseSession implements IAuthenticationManager {
266289
duration
267290
});
268291

269-
const timeToSubtract = safetyMargin || DEFAULT_SAFETY_MARGIN;
270-
271292
const session = new SessionClass({
272293
startSessionUrl: startSessionUrl,
273294
token: sessionResponse.sessionToken,
274295
styleFamily,
275296
authentication,
276-
safetyMargin: timeToSubtract,
277-
expires: new Date(sessionResponse.endTime - timeToSubtract),
297+
safetyMargin,
298+
expires: new Date(sessionResponse.endTime - safetyMargin),
278299
startTime: new Date(sessionResponse.startTime),
279300
endTime: new Date(sessionResponse.endTime),
280301
duration
281302
});
282303

304+
session.startCheckingExpirationTime();
305+
306+
if (autoRefresh) {
307+
session.startAutoRefresh();
308+
}
309+
283310
return session as T;
284311
}
285312

@@ -298,9 +325,6 @@ export abstract class BaseSession implements IAuthenticationManager {
298325
*/
299326
getToken(): Promise<string> {
300327
if (this.isExpired) {
301-
console.log(
302-
"BasmapStyleSession.getToken(): Session expired, refreshing credentials..."
303-
);
304328
return this.refreshCredentials().then(() => this.token);
305329
}
306330

@@ -316,13 +340,23 @@ export abstract class BaseSession implements IAuthenticationManager {
316340
return true;
317341
}
318342

343+
/**
344+
* Indicates if the session is set to automatically refresh when it expires.
345+
*
346+
* @returns {boolean} - Returns true if auto-refresh is enabled, otherwise false.
347+
*/
348+
get autoRefresh(): boolean {
349+
return !!this.autoRefreshHandler && !!this.expirationTimerId;
350+
}
351+
319352
/**
320353
* Refreshes the session credentials by starting a new session.
321354
* This will emit a "refreshed" event with the previous and current session details.
322355
*
323356
* @returns A promise that resolves to the current instance of the session.
324357
*/
325358
async refreshCredentials(): Promise<this> {
359+
// @TODO switch this to structured clone when we upgrade to Node 20+ types so we don't have to parse the dates later
326360
const previous = JSON.parse(
327361
JSON.stringify({
328362
token: this.token,
@@ -332,30 +366,69 @@ export abstract class BaseSession implements IAuthenticationManager {
332366
})
333367
);
334368

335-
const newSession = await startNewSession({
336-
startSessionUrl: this.startSessionUrl,
337-
styleFamily: this.styleFamily,
338-
authentication: this.authentication,
339-
duration: this.duration
340-
});
341-
342-
this.setToken(newSession.sessionToken);
343-
this.setStartTime(new Date(newSession.startTime));
344-
this.setEndTime(new Date(newSession.endTime));
345-
this.setExpires(new Date(newSession.endTime - this.saftyMargin));
369+
try {
370+
const newSession = await startNewSession({
371+
startSessionUrl: this.startSessionUrl,
372+
styleFamily: this.styleFamily,
373+
authentication: this.authentication,
374+
duration: this.duration
375+
});
346376

347-
this.emitter.emit("refreshed", {
348-
previous,
349-
current: {
350-
token: this.token,
351-
startTime: this.startTime,
352-
endTime: this.endTime,
353-
expires: this.expires
354-
}
355-
});
377+
this.setToken(newSession.sessionToken);
378+
this.setStartTime(new Date(newSession.startTime));
379+
this.setEndTime(new Date(newSession.endTime));
380+
this.setExpires(new Date(newSession.endTime - this.saftyMargin));
381+
382+
this.emitter.emit("refreshed", {
383+
previous: {
384+
token: previous.token,
385+
startTime: new Date(previous.startTime),
386+
endTime: new Date(previous.endTime),
387+
expires: new Date(previous.expires)
388+
},
389+
current: {
390+
token: this.token,
391+
startTime: this.startTime,
392+
endTime: this.endTime,
393+
expires: this.expires
394+
}
395+
});
396+
} catch (error) {
397+
this.emitter.emit("error", error);
398+
throw error;
399+
}
356400

357401
return this;
358402
}
403+
/**
404+
* Enables auto-refresh for the session. This will automatically refresh the session when it expires.
405+
* It will also start checking the expiration time of the session if it is not already started via {@linkcode BaseSession.startCheckingExpirationTime}.
406+
*/
407+
startAutoRefresh() {
408+
if (!this.expirationTimerId) {
409+
this.startCheckingExpirationTime();
410+
}
411+
412+
this.autoRefreshHandler = () => {
413+
this.refreshCredentials().catch((error: Error) => {
414+
this.emitter.emit("error", error);
415+
});
416+
};
417+
418+
this.on("expired", this.autoRefreshHandler);
419+
}
420+
421+
/**
422+
* Disables auto-refresh for the session. This will stop automatically refreshing the session when it expires.
423+
* This will **not** stop checking the expiration time of the session. If you want to stop automated expiration
424+
* checking, call {@linkcode BaseSession.stopCheckingExpirationTime} after calling this method.
425+
*/
426+
stopAutoRefresh() {
427+
if (this.autoRefreshHandler) {
428+
this.off("expired", this.autoRefreshHandler);
429+
this.autoRefreshHandler = null;
430+
}
431+
}
359432

360433
/**
361434
* A handler that listens for an eventName and returns custom handler.
@@ -365,9 +438,13 @@ export abstract class BaseSession implements IAuthenticationManager {
365438
*/
366439
on(event: "refreshed", handler: typeof BaseSession.refreshed): void;
367440
on(event: "expired", handler: typeof BaseSession.expired): void;
441+
on(event: "error", handler: typeof BaseSession.error): void;
368442
on(
369443
eventName: string,
370-
handler: typeof BaseSession.refreshed | typeof BaseSession.expired
444+
handler:
445+
| typeof BaseSession.refreshed
446+
| typeof BaseSession.expired
447+
| typeof BaseSession.error
371448
) {
372449
this.emitter.on(eventName, handler);
373450
this.isSessionExpired(); // check if the session is expired immediately after adding the handler
@@ -381,9 +458,14 @@ export abstract class BaseSession implements IAuthenticationManager {
381458
*/
382459
once(event: "refreshed", handler: typeof BaseSession.refreshed): void;
383460
once(event: "expired", handler: typeof BaseSession.expired): void;
461+
once(event: "error", handler: typeof BaseSession.error): void;
462+
384463
once(
385464
eventName: string,
386-
handler: typeof BaseSession.refreshed | typeof BaseSession.expired
465+
handler:
466+
| typeof BaseSession.refreshed
467+
| typeof BaseSession.expired
468+
| typeof BaseSession.error
387469
) {
388470
const fn = (e: any) => {
389471
this.emitter.off(eventName, fn);
@@ -401,9 +483,13 @@ export abstract class BaseSession implements IAuthenticationManager {
401483
*/
402484
off(event: "refreshed", handler: typeof BaseSession.refreshed): void;
403485
off(event: "expired", handler: typeof BaseSession.expired): void;
486+
off(event: "error", handler: typeof BaseSession.error): void;
404487
off(
405488
eventName: string,
406-
handler: typeof BaseSession.refreshed | typeof BaseSession.expired
489+
handler:
490+
| typeof BaseSession.refreshed
491+
| typeof BaseSession.expired
492+
| typeof BaseSession.error
407493
) {
408494
this.emitter.off(eventName, handler);
409495
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@ import {
33
IBasemapSessionParams,
44
IStartSessionParams
55
} from "./BaseSession.js";
6-
import { DEFAULT_START_BASEMAP_SESSION_URL } from "./utils/defaults.js";
6+
import { DEFAULT_START_BASEMAP_STYLE_SESSION_URL } from "./utils/defaults.js";
77

88
/**
99
* `BasemapStyleSession` is a class that extends {@linkcode BaseSession} to manage sessions
10-
* for basemap styles. It provides methods to {@linkcode BasemapStyleSession.start} a new session and {@linkcode BasemapStyleSession.deserialize} an existing session.
10+
* for basemap styles. It provides methods to {@linkcode BasemapStyleSession.start} a new session
11+
* which should be used instead of constructing a new instance directly.
1112
*
1213
* @class BasemapStyleSession
1314
* @extends BaseSession
1415
*/
1516
export class BasemapStyleSession extends BaseSession {
17+
/**
18+
* Creates an instance of `BasemapStyleSession`. Constructing `BasemapStyleSession` directly is discouraged.
19+
* Instead, use the static method {@linkcode BasemapStyleSession.start} to start a new session.
20+
*/
1621
constructor(params: IBasemapSessionParams) {
1722
super(params);
1823
}
@@ -24,7 +29,8 @@ export class BasemapStyleSession extends BaseSession {
2429
return BaseSession.startSession<BasemapStyleSession>(
2530
{
2631
...params,
27-
startSessionUrl: DEFAULT_START_BASEMAP_SESSION_URL
32+
startSessionUrl:
33+
params?.startSessionUrl || DEFAULT_START_BASEMAP_STYLE_SESSION_URL
2834
},
2935
BasemapStyleSession as new (
3036
params: IBasemapSessionParams

0 commit comments

Comments
 (0)