@thi.ng/server 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +6 -4
- package/api.d.ts +13 -10
- package/interceptors/auth-route.d.ts +7 -6
- package/interceptors/logging.d.ts +1 -1
- package/interceptors/logging.js +0 -1
- package/interceptors/measure.js +0 -1
- package/package.json +18 -18
- package/server.js +27 -19
- package/session/memory.d.ts +2 -2
- package/session/session.d.ts +33 -3
- package/session/session.js +20 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-02-
|
|
3
|
+
- **Last updated**: 2025-02-19T20:59:58Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
## [0.5.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/server@0.5.0) (2025-02-19)
|
|
15
|
+
|
|
16
|
+
#### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- update interceptor handling ([9cdb8b8](https://github.com/thi-ng/umbrella/commit/9cdb8b8))
|
|
19
|
+
- update post-interceptor execution logic & return values
|
|
20
|
+
- update `Server.runHandler()`, `Server.compileRoute()`
|
|
21
|
+
- update `logResponse()`, `measure()` interceptors
|
|
22
|
+
|
|
23
|
+
#### ♻️ Refactoring
|
|
24
|
+
|
|
25
|
+
- rename `serverSession()` => `sessionInterceptor()` ([2ada168](https://github.com/thi-ng/umbrella/commit/2ada168))
|
|
26
|
+
- add docs
|
|
27
|
+
|
|
14
28
|
## [0.4.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/server@0.4.0) (2025-02-10)
|
|
15
29
|
|
|
16
30
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://mastodon.thi.ng/@toxi)
|
|
8
8
|
|
|
9
9
|
> [!NOTE]
|
|
10
|
-
> This is one of
|
|
10
|
+
> This is one of 202 standalone projects, maintained as part
|
|
11
11
|
> of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
|
|
12
12
|
> and anti-framework.
|
|
13
13
|
>
|
|
@@ -80,7 +80,7 @@ for more details.
|
|
|
80
80
|
- [`logResponse()`](https://docs.thi.ng/umbrella/server/functions/logResponse.html): Response logging
|
|
81
81
|
- [`rateLimiter()`](https://docs.thi.ng/umbrella/server/functions/rateLimiter-1.html): Configurable rate limiting
|
|
82
82
|
- [`referrerPolicy()`](https://docs.thi.ng/umbrella/server/functions/referrerPolicy-1.html): Policy header injection
|
|
83
|
-
- [`
|
|
83
|
+
- [`sessionInterceptor()`](https://docs.thi.ng/umbrella/server/functions/sessionInterceptor-1.html): User defined in-memory sessions with TTL
|
|
84
84
|
- [`strictTransportSecurity()`](https://docs.thi.ng/umbrella/server/functions/strictTransportSecurity.html): Policy header injection
|
|
85
85
|
|
|
86
86
|
#### Custom interceptors
|
|
@@ -149,7 +149,7 @@ For Node.js REPL:
|
|
|
149
149
|
const ser = await import("@thi.ng/server");
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 5.
|
|
152
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 5.28 KB
|
|
153
153
|
|
|
154
154
|
## Dependencies
|
|
155
155
|
|
|
@@ -192,7 +192,9 @@ interface AppSession extends srv.ServerSession {
|
|
|
192
192
|
|
|
193
193
|
// interceptor for injecting/managing sessions
|
|
194
194
|
// by default uses in-memory storage/cache
|
|
195
|
-
const session = srv.
|
|
195
|
+
const session = srv.sessionInterceptor<AppCtx, AppSession>({
|
|
196
|
+
factory: srv.createSession
|
|
197
|
+
});
|
|
196
198
|
|
|
197
199
|
// create server with given config
|
|
198
200
|
const app = srv.server<AppCtx>({
|
package/api.d.ts
CHANGED
|
@@ -74,7 +74,8 @@ export interface RequestCtx {
|
|
|
74
74
|
session?: ServerSession;
|
|
75
75
|
}
|
|
76
76
|
export type HandlerResult = MaybePromise<void>;
|
|
77
|
-
export type
|
|
77
|
+
export type PreInterceptorResult = MaybePromise<boolean>;
|
|
78
|
+
export type PostInterceptorResult = MaybePromise<void>;
|
|
78
79
|
export type RequestHandler<CTX extends RequestCtx = RequestCtx> = Fn<CTX, HandlerResult> | InterceptedRequestHandler<CTX>;
|
|
79
80
|
export interface InterceptedRequestHandler<CTX extends RequestCtx = RequestCtx> {
|
|
80
81
|
fn: Fn<CTX, HandlerResult>;
|
|
@@ -98,25 +99,27 @@ export interface InterceptedRequestHandler<CTX extends RequestCtx = RequestCtx>
|
|
|
98
99
|
}
|
|
99
100
|
export interface CompiledHandler<CTX extends RequestCtx = RequestCtx> {
|
|
100
101
|
fn: Fn<CTX, HandlerResult>;
|
|
101
|
-
pre?: Fn<CTX,
|
|
102
|
-
post?: Fn<CTX,
|
|
102
|
+
pre?: Maybe<Fn<CTX, PreInterceptorResult>>[];
|
|
103
|
+
post?: Maybe<Fn<CTX, PostInterceptorResult>>[];
|
|
103
104
|
}
|
|
104
105
|
export interface Interceptor<CTX extends RequestCtx = RequestCtx> {
|
|
105
106
|
/**
|
|
106
107
|
* Interceptor function which will be run BEFORE the main route handler (aka
|
|
107
108
|
* {@link InterceptedRequestHandler.fn}). If an interceptor needs to cancel
|
|
108
109
|
* the request processing it must return `false`. In this case any further
|
|
109
|
-
* pre-interceptors
|
|
110
|
-
*
|
|
110
|
+
* pre-interceptors and the main handler will be skipped. In the post-phase,
|
|
111
|
+
* only the interceptors preceding the failed one will be run (though in
|
|
112
|
+
* reverse order). E.g. If the 3rd pre-interceptor failed, only the post
|
|
113
|
+
* phases of the first two will still be run (if available)...
|
|
111
114
|
*/
|
|
112
|
-
pre?: Fn<CTX,
|
|
115
|
+
pre?: Fn<CTX, PreInterceptorResult>;
|
|
113
116
|
/**
|
|
114
117
|
* Interceptor function which will be run AFTER the main route handler (aka
|
|
115
|
-
* {@link InterceptedRequestHandler.fn}).
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
+
* {@link InterceptedRequestHandler.fn}). Post-interceptors cannot cancel
|
|
119
|
+
* request processing and are mainly intended for logging or clean-up
|
|
120
|
+
* purposes. Post interceptors
|
|
118
121
|
*/
|
|
119
|
-
post?: Fn<CTX,
|
|
122
|
+
post?: Fn<CTX, PostInterceptorResult>;
|
|
120
123
|
}
|
|
121
124
|
export interface ServerSession {
|
|
122
125
|
/**
|
|
@@ -2,14 +2,15 @@ import type { Predicate } from "@thi.ng/api";
|
|
|
2
2
|
import type { Interceptor, RequestCtx } from "../api.js";
|
|
3
3
|
/**
|
|
4
4
|
* Pre-interceptor. Checks if current route has `auth` flag enabled and if so
|
|
5
|
-
* applies given predicate function to {@link RequestCtx}. If the
|
|
6
|
-
* returns false, the server responds with
|
|
7
|
-
* following pre-interceptors and
|
|
8
|
-
* post-interceptors (if any) will
|
|
5
|
+
* applies given predicate function to current {@link RequestCtx}. If the
|
|
6
|
+
* predicate returns false, the server responds with
|
|
7
|
+
* {@link ServerResponse.unauthorized} and any following pre-interceptors and
|
|
8
|
+
* the main route handler will be skipped. Only post-interceptors (if any) will
|
|
9
|
+
* still be executed.
|
|
9
10
|
*
|
|
10
11
|
* @remarks
|
|
11
|
-
* If this interceptor is used with the {@link
|
|
12
|
-
* MUST come after it, otherwise the session information will not yet be
|
|
12
|
+
* If this interceptor is used with the {@link sessionInterceptor} interceptor,
|
|
13
|
+
* it MUST come after it, otherwise the session information will not yet be
|
|
13
14
|
* available in the context object given to the predicate.
|
|
14
15
|
*
|
|
15
16
|
* @param pred
|
|
@@ -9,7 +9,7 @@ import type { Interceptor } from "../api.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const logRequest: (level?: LogLevel | LogLevelName) => Interceptor;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Post-interceptor to log response details (status, route, headers) using the
|
|
13
13
|
* server's {@link ServerOpts.logger}. The `level` arg can be used to customize
|
|
14
14
|
* which log level to use.
|
|
15
15
|
*
|
package/interceptors/logging.js
CHANGED
package/interceptors/measure.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Minimal HTTP server with declarative routing, static file serving and freely extensible via pre/post interceptors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -39,24 +39,24 @@
|
|
|
39
39
|
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@thi.ng/api": "^8.11.
|
|
43
|
-
"@thi.ng/arrays": "^2.10.
|
|
44
|
-
"@thi.ng/cache": "^2.3.
|
|
45
|
-
"@thi.ng/checks": "^3.6.
|
|
46
|
-
"@thi.ng/errors": "^2.5.
|
|
47
|
-
"@thi.ng/file-io": "^2.1.
|
|
48
|
-
"@thi.ng/logger": "^3.1.
|
|
49
|
-
"@thi.ng/mime": "^2.7.
|
|
50
|
-
"@thi.ng/paths": "^5.2.
|
|
51
|
-
"@thi.ng/router": "^4.1.
|
|
52
|
-
"@thi.ng/strings": "^3.9.
|
|
53
|
-
"@thi.ng/timestamp": "^1.1.
|
|
54
|
-
"@thi.ng/uuid": "^1.1.
|
|
42
|
+
"@thi.ng/api": "^8.11.21",
|
|
43
|
+
"@thi.ng/arrays": "^2.10.16",
|
|
44
|
+
"@thi.ng/cache": "^2.3.24",
|
|
45
|
+
"@thi.ng/checks": "^3.6.24",
|
|
46
|
+
"@thi.ng/errors": "^2.5.27",
|
|
47
|
+
"@thi.ng/file-io": "^2.1.28",
|
|
48
|
+
"@thi.ng/logger": "^3.1.2",
|
|
49
|
+
"@thi.ng/mime": "^2.7.3",
|
|
50
|
+
"@thi.ng/paths": "^5.2.2",
|
|
51
|
+
"@thi.ng/router": "^4.1.19",
|
|
52
|
+
"@thi.ng/strings": "^3.9.6",
|
|
53
|
+
"@thi.ng/timestamp": "^1.1.6",
|
|
54
|
+
"@thi.ng/uuid": "^1.1.18"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@types/node": "^22.
|
|
58
|
-
"esbuild": "^0.
|
|
59
|
-
"typedoc": "^0.27.
|
|
57
|
+
"@types/node": "^22.13.1",
|
|
58
|
+
"esbuild": "^0.25.0",
|
|
59
|
+
"typedoc": "^0.27.7",
|
|
60
60
|
"typescript": "^5.7.3"
|
|
61
61
|
},
|
|
62
62
|
"keywords": [
|
|
@@ -163,5 +163,5 @@
|
|
|
163
163
|
"status": "alpha",
|
|
164
164
|
"year": 2024
|
|
165
165
|
},
|
|
166
|
-
"gitHead": "
|
|
166
|
+
"gitHead": "bee617702ac61d093465b967f8f973dc566faa6b\n"
|
|
167
167
|
}
|
package/server.js
CHANGED
|
@@ -127,19 +127,25 @@ class Server {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
async runHandler({ fn, pre, post }, ctx) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
try {
|
|
131
|
+
let failed;
|
|
132
|
+
if (pre) {
|
|
133
|
+
for (let i = 0, n = pre.length; i < n; i++) {
|
|
134
|
+
const fn2 = pre[i];
|
|
135
|
+
if (fn2 && !await fn2(ctx)) {
|
|
136
|
+
ctx.res.end();
|
|
137
|
+
failed = i;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (failed === void 0) await fn(ctx);
|
|
143
|
+
if (post) {
|
|
144
|
+
for (let i = failed ?? post.length; --i >= 0; ) {
|
|
145
|
+
const fn2 = post[i];
|
|
146
|
+
if (fn2) await fn2(ctx);
|
|
135
147
|
}
|
|
136
148
|
}
|
|
137
|
-
return true;
|
|
138
|
-
};
|
|
139
|
-
try {
|
|
140
|
-
if (pre && !await runPhase(pre)) return;
|
|
141
|
-
await fn(ctx);
|
|
142
|
-
if (post && !await runPhase(post)) return;
|
|
143
149
|
ctx.res.end();
|
|
144
150
|
} catch (e) {
|
|
145
151
|
this.logger.warn(`handler error:`, e);
|
|
@@ -150,16 +156,18 @@ class Server {
|
|
|
150
156
|
}
|
|
151
157
|
compileRoute(route) {
|
|
152
158
|
const compilePhase = (handler, phase) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (x[phase])
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
for (let x of handler.intercept ?? []) {
|
|
159
|
-
if (x[phase]) fns.push(x[phase].bind(x));
|
|
159
|
+
let isPhaseUsed = false;
|
|
160
|
+
const $bind = (iceps) => (iceps ?? []).map((x) => {
|
|
161
|
+
if (x[phase]) {
|
|
162
|
+
isPhaseUsed = true;
|
|
163
|
+
return x[phase].bind(x);
|
|
160
164
|
}
|
|
165
|
+
});
|
|
166
|
+
const fns = [...$bind(this.opts.intercept)];
|
|
167
|
+
if (!isFunction(handler)) {
|
|
168
|
+
fns.push(...$bind(handler.intercept));
|
|
161
169
|
}
|
|
162
|
-
return
|
|
170
|
+
return isPhaseUsed ? fns : void 0;
|
|
163
171
|
};
|
|
164
172
|
const result = { ...route, handlers: {} };
|
|
165
173
|
for (let method in route.handlers) {
|
package/session/memory.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export interface InMemorySessionOpts<T extends ServerSession = ServerSession> {
|
|
|
21
21
|
initial: Record<string, T>;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Session storage implementation for use with {@link
|
|
25
|
-
* in-memory TLRU Cache with configurable TTL.
|
|
24
|
+
* Session storage implementation for use with {@link sessionInterceptor}, using
|
|
25
|
+
* an in-memory TLRU Cache with configurable TTL.
|
|
26
26
|
*/
|
|
27
27
|
export declare class InMemorySessionStore<T extends ServerSession = ServerSession> implements ISessionStore<T> {
|
|
28
28
|
readonly ttl: number;
|
package/session/session.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { Fn } from "@thi.ng/api";
|
|
2
2
|
import { ServerResponse } from "node:http";
|
|
3
3
|
import type { Interceptor, ISessionStore, RequestCtx, ServerSession } from "../api.js";
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for {@link SessionInterceptor}.
|
|
6
|
+
*/
|
|
4
7
|
export interface SessionOpts<CTX extends RequestCtx = RequestCtx, SESSION extends ServerSession = ServerSession> {
|
|
5
8
|
/**
|
|
6
|
-
* Factory function to create a new session object. See
|
|
9
|
+
* Factory function to create a new session object. See
|
|
10
|
+
* {@link createSession} for a base implementation.
|
|
7
11
|
*/
|
|
8
12
|
factory: Fn<CTX, SESSION>;
|
|
9
13
|
/**
|
|
@@ -28,6 +32,13 @@ export interface SessionOpts<CTX extends RequestCtx = RequestCtx, SESSION extend
|
|
|
28
32
|
*/
|
|
29
33
|
secret?: number | string | Buffer;
|
|
30
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Pre-interceptor which parses & validates session cookie (if available) from
|
|
37
|
+
* current request and injects/updates session cookie in response. Only a signed
|
|
38
|
+
* session ID will be stored in the cookie. Thr actual session data is held
|
|
39
|
+
* server side, via configured session storage (see {@link SessionOpts.store})
|
|
40
|
+
* (by default uses {@link InMemorySessionStore}).
|
|
41
|
+
*/
|
|
31
42
|
export declare class SessionInterceptor<CTX extends RequestCtx = RequestCtx, SESSION extends ServerSession = ServerSession> implements Interceptor<CTX> {
|
|
32
43
|
factory: Fn<CTX, SESSION>;
|
|
33
44
|
store: ISessionStore<SESSION>;
|
|
@@ -36,7 +47,25 @@ export declare class SessionInterceptor<CTX extends RequestCtx = RequestCtx, SES
|
|
|
36
47
|
secret: Buffer;
|
|
37
48
|
constructor({ factory, store, cookieName, cookieOpts, secret, }: SessionOpts<CTX, SESSION>);
|
|
38
49
|
pre(ctx: CTX): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Attempts to delete session for given ID and if successful also sets
|
|
52
|
+
* force-expired cookie in response.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Intended for logout handlers and/or switching sessions when a user has
|
|
56
|
+
* successfully authenticated (to avoid session fixation).
|
|
57
|
+
*
|
|
58
|
+
* @param ctx
|
|
59
|
+
* @param sessionID
|
|
60
|
+
*/
|
|
39
61
|
deleteSession(ctx: CTX, sessionID: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new session object (via configured {@link SessionOpts.factory})
|
|
64
|
+
* and submits it to configured {@link SessionOpts.store}, Returns session
|
|
65
|
+
* if successful, otherwise returns `undefined`.
|
|
66
|
+
*
|
|
67
|
+
* @param ctx
|
|
68
|
+
*/
|
|
40
69
|
newSession(ctx: CTX): Promise<SESSION | undefined>;
|
|
41
70
|
/**
|
|
42
71
|
* Calls {@link SessionInterceptor.newSession} to create a new session and,
|
|
@@ -50,11 +79,12 @@ export declare class SessionInterceptor<CTX extends RequestCtx = RequestCtx, SES
|
|
|
50
79
|
validateSession(cookie: string): string | undefined;
|
|
51
80
|
}
|
|
52
81
|
/**
|
|
53
|
-
* Factory function to create a new {@link SessionInterceptor} instance
|
|
82
|
+
* Factory function to create a new {@link SessionInterceptor} instance
|
|
83
|
+
* configured with given options.
|
|
54
84
|
*
|
|
55
85
|
* @param opts
|
|
56
86
|
*/
|
|
57
|
-
export declare const
|
|
87
|
+
export declare const sessionInterceptor: <CTX extends RequestCtx = RequestCtx, SESSION extends ServerSession = ServerSession>(opts: SessionOpts<CTX, SESSION>) => SessionInterceptor<CTX, SESSION>;
|
|
58
88
|
/**
|
|
59
89
|
* Creates a new basic {@link ServerSession}, using a UUID v4 for
|
|
60
90
|
* {@link ServerSession.id}.
|
package/session/session.js
CHANGED
|
@@ -41,6 +41,17 @@ class SessionInterceptor {
|
|
|
41
41
|
this.withSession(ctx.res, session.id);
|
|
42
42
|
return true;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Attempts to delete session for given ID and if successful also sets
|
|
46
|
+
* force-expired cookie in response.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* Intended for logout handlers and/or switching sessions when a user has
|
|
50
|
+
* successfully authenticated (to avoid session fixation).
|
|
51
|
+
*
|
|
52
|
+
* @param ctx
|
|
53
|
+
* @param sessionID
|
|
54
|
+
*/
|
|
44
55
|
async deleteSession(ctx, sessionID) {
|
|
45
56
|
if (await this.store.delete(sessionID)) {
|
|
46
57
|
ctx.logger.info("delete session:", sessionID);
|
|
@@ -51,6 +62,13 @@ class SessionInterceptor {
|
|
|
51
62
|
);
|
|
52
63
|
}
|
|
53
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new session object (via configured {@link SessionOpts.factory})
|
|
67
|
+
* and submits it to configured {@link SessionOpts.store}, Returns session
|
|
68
|
+
* if successful, otherwise returns `undefined`.
|
|
69
|
+
*
|
|
70
|
+
* @param ctx
|
|
71
|
+
*/
|
|
54
72
|
async newSession(ctx) {
|
|
55
73
|
const session = this.factory(ctx);
|
|
56
74
|
ctx.logger.info("new session:", session.id);
|
|
@@ -96,7 +114,7 @@ class SessionInterceptor {
|
|
|
96
114
|
return timingSafeEqual(sameLength ? actual : expected, expected) && sameLength ? parts[0] : void 0;
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
|
-
const
|
|
117
|
+
const sessionInterceptor = (opts) => new SessionInterceptor(opts);
|
|
100
118
|
const createSession = (ctx) => ({
|
|
101
119
|
id: uuid(),
|
|
102
120
|
ip: ctx.req.socket.remoteAddress
|
|
@@ -104,5 +122,5 @@ const createSession = (ctx) => ({
|
|
|
104
122
|
export {
|
|
105
123
|
SessionInterceptor,
|
|
106
124
|
createSession,
|
|
107
|
-
|
|
125
|
+
sessionInterceptor
|
|
108
126
|
};
|