@randajan/koa-io-session 2.2.0 → 3.0.1
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/README.md +166 -104
- package/dist/cjs/index.cjs +433 -209
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/cjs/stores/FileStore.cjs +117 -0
- package/dist/cjs/stores/FileStore.cjs.map +7 -0
- package/dist/esm/chunk-NKL2ZYZW.js +110 -0
- package/dist/esm/chunk-NKL2ZYZW.js.map +7 -0
- package/dist/esm/index.mjs +393 -251
- package/dist/esm/index.mjs.map +4 -4
- package/dist/esm/stores/FileStore.mjs +66 -0
- package/dist/esm/stores/FileStore.mjs.map +7 -0
- package/package.json +8 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -1,70 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
_customOptKeys,
|
|
3
|
+
_errPrefix,
|
|
4
|
+
_privs,
|
|
5
|
+
generateUid,
|
|
6
|
+
is,
|
|
7
|
+
ms,
|
|
8
|
+
valid,
|
|
9
|
+
validArray,
|
|
10
|
+
validInterval,
|
|
11
|
+
validObject,
|
|
12
|
+
validRange,
|
|
13
|
+
validStore
|
|
14
|
+
} from "./chunk-NKL2ZYZW.js";
|
|
15
|
+
|
|
1
16
|
// src/class/SessionBridge.js
|
|
2
|
-
import { EventEmitter
|
|
17
|
+
import { EventEmitter } from "events";
|
|
3
18
|
import { ServerResponse } from "http";
|
|
4
19
|
import { solid as solid2, virtual } from "@randajan/props";
|
|
5
20
|
|
|
6
|
-
// src/tools.js
|
|
7
|
-
import crypto from "crypto";
|
|
8
|
-
var generateUid = (len = 16) => crypto.randomBytes(len).toString("base64url").slice(0, len);
|
|
9
|
-
var is = (type, any) => typeof any === type;
|
|
10
|
-
var valid = (type, any, req = false, msg = "argument") => {
|
|
11
|
-
if (any == null) {
|
|
12
|
-
if (!req) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
throw new Error(`${msg} require typeof '${type}'`);
|
|
16
|
-
}
|
|
17
|
-
if (is(type, any)) {
|
|
18
|
-
return any;
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`${msg} is not typeof '${type}'`);
|
|
21
|
-
};
|
|
22
|
-
var validRange = (min, max, any, req = false, msg = "argument") => {
|
|
23
|
-
const num = valid("number", any, req, msg);
|
|
24
|
-
if (num == null) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (num < min) {
|
|
28
|
-
throw new Error(`${msg} must be greater than ${min}`);
|
|
29
|
-
}
|
|
30
|
-
if (num > max) {
|
|
31
|
-
throw new Error(`${msg} must be less than ${max}`);
|
|
32
|
-
}
|
|
33
|
-
return num;
|
|
34
|
-
};
|
|
35
|
-
var validInterval = (any, req = false, msg = "argument") => {
|
|
36
|
-
return validRange(10, 2147483647, any, req, msg);
|
|
37
|
-
};
|
|
38
|
-
var validObject = (any, req = false, msg = "argument") => {
|
|
39
|
-
const obj = valid("object", any, req, msg);
|
|
40
|
-
if (obj == null) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (!Array.isArray(obj)) {
|
|
44
|
-
return obj;
|
|
45
|
-
}
|
|
46
|
-
throw new Error(`${msg} must be object, not array`);
|
|
47
|
-
};
|
|
48
|
-
var validStore = (store) => {
|
|
49
|
-
const missing = [];
|
|
50
|
-
if (!is("function", store?.get)) {
|
|
51
|
-
missing.push("get()");
|
|
52
|
-
}
|
|
53
|
-
if (!is("function", store?.set)) {
|
|
54
|
-
missing.push("set()");
|
|
55
|
-
}
|
|
56
|
-
if (!is("function", store?.destroy)) {
|
|
57
|
-
missing.push("destroy()");
|
|
58
|
-
}
|
|
59
|
-
if (!is("function", store?.on)) {
|
|
60
|
-
missing.push("on()");
|
|
61
|
-
}
|
|
62
|
-
if (missing.length) {
|
|
63
|
-
throw new TypeError(`store is missing required API: ${missing.join(", ")}`);
|
|
64
|
-
}
|
|
65
|
-
return store;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
21
|
// src/httpSession.js
|
|
69
22
|
import session from "koa-session";
|
|
70
23
|
|
|
@@ -88,10 +41,32 @@ var wrapExternalKey = (opt, onSet) => {
|
|
|
88
41
|
};
|
|
89
42
|
|
|
90
43
|
// src/httpSession.js
|
|
91
|
-
var
|
|
92
|
-
|
|
44
|
+
var ensureAppKeys = (app, keys, allowRnd = false) => {
|
|
45
|
+
if (app.keys) {
|
|
46
|
+
if (!keys) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`${_errPrefix} Cannot set 'appKeys' because app.keys is already defined.`);
|
|
50
|
+
}
|
|
51
|
+
if (keys) {
|
|
52
|
+
app.keys = keys;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
app.keys = keys = Array(2).fill().map(() => generateUid(32));
|
|
56
|
+
if (!allowRnd) {
|
|
57
|
+
console.warn([
|
|
58
|
+
`${_errPrefix} app.keys were generated at runtime.`,
|
|
59
|
+
`${_errPrefix} Resolve this by adding one of these options to bridge constructor:`,
|
|
60
|
+
`${_errPrefix} 1) "appKeys": ${JSON.stringify(app.keys)}`,
|
|
61
|
+
`${_errPrefix} 2) "allowRndAppKeys": true`
|
|
62
|
+
].join("\n"));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var createKoaSession = (app, gw, opt, onSet) => {
|
|
66
|
+
const maxAge = gw.maxAge;
|
|
67
|
+
const store = wrapStore(gw);
|
|
93
68
|
const externalKey = wrapExternalKey(opt, onSet);
|
|
94
|
-
const koaSession = session({ ...opt, store, externalKey }, app);
|
|
69
|
+
const koaSession = session({ ...opt, maxAge, store, externalKey }, app);
|
|
95
70
|
return [koaSession, externalKey];
|
|
96
71
|
};
|
|
97
72
|
var createClientCookie = (opt) => {
|
|
@@ -111,41 +86,6 @@ var createClientCookie = (opt) => {
|
|
|
111
86
|
// src/socketSession.js
|
|
112
87
|
import { solids } from "@randajan/props";
|
|
113
88
|
var sidLocks = /* @__PURE__ */ new Map();
|
|
114
|
-
var createSessionCtx = (sessionId, session2, socket) => solids({ session: session2 }, { sessionId, socket });
|
|
115
|
-
var createSessionHash = (session2) => {
|
|
116
|
-
try {
|
|
117
|
-
return JSON.stringify(session2 ?? null);
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
var isSessionHashChanged = (originalHash, session2) => {
|
|
123
|
-
const nextHash = createSessionHash(session2);
|
|
124
|
-
if (originalHash == null || nextHash == null) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
return originalHash !== nextHash;
|
|
128
|
-
};
|
|
129
|
-
var withLock = async (task, socket, ...args) => {
|
|
130
|
-
const sid = socket.sessionId;
|
|
131
|
-
const previous = sidLocks.get(sid);
|
|
132
|
-
let releaseCurrent;
|
|
133
|
-
const current = new Promise((resolve) => {
|
|
134
|
-
releaseCurrent = resolve;
|
|
135
|
-
});
|
|
136
|
-
sidLocks.set(sid, current);
|
|
137
|
-
if (previous) {
|
|
138
|
-
await previous;
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
return await task(socket, ...args);
|
|
142
|
-
} finally {
|
|
143
|
-
releaseCurrent();
|
|
144
|
-
if (sidLocks.get(sid) === current) {
|
|
145
|
-
sidLocks.delete(sid);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
89
|
var applyOnMissing = (onMissing) => {
|
|
150
90
|
if (onMissing instanceof Error) {
|
|
151
91
|
throw onMissing;
|
|
@@ -155,126 +95,55 @@ var applyOnMissing = (onMissing) => {
|
|
|
155
95
|
}
|
|
156
96
|
return onMissing;
|
|
157
97
|
};
|
|
158
|
-
var runSessionHandler = async (socket, handler,
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
if (!current) {
|
|
98
|
+
var runSessionHandler = async (sid, socket, handler, gw, onMissing) => {
|
|
99
|
+
const session2 = await gw.get(sid);
|
|
100
|
+
if (!session2) {
|
|
162
101
|
return applyOnMissing(onMissing);
|
|
163
102
|
}
|
|
164
|
-
const
|
|
165
|
-
const sessionCtx = createSessionCtx(sid, session2, socket);
|
|
166
|
-
const originalHash = createSessionHash(sessionCtx.session);
|
|
103
|
+
const sessionCtx = solids({ session: session2 }, { sessionId: sid, socket });
|
|
167
104
|
const result = await handler(sessionCtx, socket);
|
|
168
|
-
if (
|
|
169
|
-
await
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
sessionCtx.session = validObject(sessionCtx.session, false, "session");
|
|
173
|
-
if (isSessionHashChanged(originalHash, sessionCtx.session)) {
|
|
174
|
-
await store.set(sid, sessionCtx.session);
|
|
105
|
+
if (sid === socket.sessionId) {
|
|
106
|
+
await gw.set(sid, sessionCtx.session);
|
|
175
107
|
}
|
|
176
108
|
return result;
|
|
177
109
|
};
|
|
178
|
-
var
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
import { solid } from "@randajan/props";
|
|
190
|
-
import { EventEmitter } from "events";
|
|
191
|
-
|
|
192
|
-
// src/const.js
|
|
193
|
-
var ms = {
|
|
194
|
-
s: (v = 1) => v * 1e3,
|
|
195
|
-
m: (v = 1) => ms.s(v * 60),
|
|
196
|
-
h: (v = 1) => ms.m(v * 60),
|
|
197
|
-
d: (v = 1) => ms.h(v * 24),
|
|
198
|
-
w: (v = 1) => ms.d(v * 7),
|
|
199
|
-
M: (v = 1) => ms.d(v * 30),
|
|
200
|
-
y: (v = 1) => ms.d(v * 365)
|
|
201
|
-
};
|
|
202
|
-
var _customOptKeys = /* @__PURE__ */ new Set([
|
|
203
|
-
"store",
|
|
204
|
-
"autoCleanup",
|
|
205
|
-
"autoCleanupMs",
|
|
206
|
-
"clientKey",
|
|
207
|
-
"clientMaxAge",
|
|
208
|
-
"clientAlwaysRoll"
|
|
209
|
-
]);
|
|
210
|
-
|
|
211
|
-
// src/class/SessionStore.js
|
|
212
|
-
var formatState = (session2, maxAge, prevTTL, maxAgeDefault) => {
|
|
213
|
-
const ttl = maxAge ?? prevTTL ?? maxAgeDefault;
|
|
214
|
-
const expiresAt = Date.now() + ttl;
|
|
215
|
-
return { session: session2, expiresAt, ttl };
|
|
216
|
-
};
|
|
217
|
-
var SessionStore = class extends Map {
|
|
218
|
-
constructor(opt = {}) {
|
|
219
|
-
super();
|
|
220
|
-
const maxAge = validRange(ms.s(), ms.y(), opt.maxAge, false, "maxAge") ?? ms.M();
|
|
221
|
-
const autoCleanup = valid("boolean", opt.autoCleanup, false, "autoCleanup") ?? true;
|
|
222
|
-
const autoCleanupMs = validInterval(opt.autoCleanupMs, false, "autoCleanupMs") ?? Math.max(ms.s(), Math.min(ms.h(), maxAge / 10));
|
|
223
|
-
solid(this, "maxAge", maxAge);
|
|
224
|
-
solid(this, "event", new EventEmitter());
|
|
225
|
-
if (!autoCleanup) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
setInterval((_) => this.cleanup(), autoCleanupMs);
|
|
229
|
-
}
|
|
230
|
-
on(eventName, callback) {
|
|
231
|
-
return this.event.on(eventName, callback);
|
|
232
|
-
}
|
|
233
|
-
get(sid) {
|
|
234
|
-
const d = super.get(sid);
|
|
235
|
-
if (!d) {
|
|
236
|
-
return;
|
|
110
|
+
var createLock = (sid) => {
|
|
111
|
+
let _release;
|
|
112
|
+
const previous = sidLocks.get(sid);
|
|
113
|
+
const current = new Promise((resolve) => {
|
|
114
|
+
_release = resolve;
|
|
115
|
+
});
|
|
116
|
+
sidLocks.set(sid, current);
|
|
117
|
+
const release = () => {
|
|
118
|
+
_release();
|
|
119
|
+
if (sidLocks.get(sid) === current) {
|
|
120
|
+
sidLocks.delete(sid);
|
|
237
121
|
}
|
|
238
|
-
|
|
239
|
-
|
|
122
|
+
};
|
|
123
|
+
return [previous, release];
|
|
124
|
+
};
|
|
125
|
+
var applySessionHandler = async (socket, handler, gw, onMissing) => {
|
|
126
|
+
valid("function", handler, true, "handler");
|
|
127
|
+
for (let i = 0; i < 5; i++) {
|
|
128
|
+
const sid = socket.sessionId;
|
|
129
|
+
if (!sid) {
|
|
130
|
+
return applyOnMissing(onMissing);
|
|
240
131
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const d = super.get(sid);
|
|
245
|
-
if (session2 == null) {
|
|
246
|
-
return !d || this.destroy(sid);
|
|
132
|
+
const [previous, release] = createLock(sid);
|
|
133
|
+
if (previous) {
|
|
134
|
+
await previous;
|
|
247
135
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
delete(sid) {
|
|
253
|
-
return this.destroy(sid);
|
|
254
|
-
}
|
|
255
|
-
destroy(sid) {
|
|
256
|
-
if (this.has(sid)) {
|
|
257
|
-
super.delete(sid);
|
|
258
|
-
this.event.emit("destroy", this, sid);
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
cleanup() {
|
|
263
|
-
const now = Date.now();
|
|
264
|
-
let cleared = 0;
|
|
265
|
-
for (const [sid, d] of this.entries()) {
|
|
266
|
-
if (now < d.expiresAt) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (this.destroy(sid)) {
|
|
270
|
-
cleared++;
|
|
271
|
-
}
|
|
136
|
+
if (previous && sid !== socket.sessionId) {
|
|
137
|
+
release();
|
|
138
|
+
continue;
|
|
272
139
|
}
|
|
273
|
-
|
|
274
|
-
|
|
140
|
+
try {
|
|
141
|
+
return await runSessionHandler(sid, socket, handler, gw, onMissing);
|
|
142
|
+
} finally {
|
|
143
|
+
release();
|
|
275
144
|
}
|
|
276
|
-
return cleared;
|
|
277
145
|
}
|
|
146
|
+
throw new Error(`${_errPrefix} socket.sessionId changed during withSession execution.`);
|
|
278
147
|
};
|
|
279
148
|
|
|
280
149
|
// src/formatOptions.js
|
|
@@ -286,20 +155,22 @@ var pickKoaOpt = (rawOpt) => {
|
|
|
286
155
|
}
|
|
287
156
|
koaOpt[key] = rawOpt[key];
|
|
288
157
|
}
|
|
289
|
-
koaOpt.key = valid("string", koaOpt.key, false, "key") ??
|
|
290
|
-
koaOpt.maxAge = validRange(ms.s(), ms.y(), koaOpt.maxAge, false, "maxAge") ?? ms.M();
|
|
158
|
+
koaOpt.key = valid("string", koaOpt.key, false, "key") ?? "sid";
|
|
291
159
|
koaOpt.signed = valid("boolean", koaOpt.signed, false, "signed") ?? true;
|
|
292
|
-
koaOpt.store = validStore(rawOpt.store || new SessionStore(rawOpt));
|
|
293
160
|
return koaOpt;
|
|
294
161
|
};
|
|
295
162
|
var formatOptions = (opt = {}) => {
|
|
296
163
|
opt = validObject(opt, true, "options");
|
|
164
|
+
const appKeys = validArray(opt.appKeys, false, "appKeys");
|
|
165
|
+
const allowRndAppKeys = valid("boolean", opt.allowRndAppKeys, false, "allowRnddAppKeys") ?? false;
|
|
297
166
|
const koaOpt = pickKoaOpt(opt);
|
|
298
|
-
const clientKey = valid("string", opt.clientKey) ??
|
|
167
|
+
const clientKey = valid("string", opt.clientKey) ?? "cid";
|
|
299
168
|
const clientMaxAge = validInterval(opt.clientMaxAge, false, "clientMaxAge") ?? ms.y();
|
|
300
169
|
const clientAlwaysRoll = valid("boolean", opt.clientAlwaysRoll, false, "clientAlwaysRoll") ?? true;
|
|
301
170
|
const clientOpt = { ...koaOpt, key: clientKey, maxAge: clientMaxAge };
|
|
302
171
|
return {
|
|
172
|
+
appKeys,
|
|
173
|
+
allowRndAppKeys,
|
|
303
174
|
koaOpt,
|
|
304
175
|
clientOpt,
|
|
305
176
|
clientAlwaysRoll
|
|
@@ -329,7 +200,7 @@ var Bridge = class {
|
|
|
329
200
|
}
|
|
330
201
|
this.c2s.set(cid, sid);
|
|
331
202
|
this.s2c.set(sid, cid);
|
|
332
|
-
this.onSet(
|
|
203
|
+
this.onSet(cid, sid);
|
|
333
204
|
return true;
|
|
334
205
|
}
|
|
335
206
|
getByCid(cid) {
|
|
@@ -348,7 +219,7 @@ var Bridge = class {
|
|
|
348
219
|
}
|
|
349
220
|
this.s2c.delete(sid);
|
|
350
221
|
this.c2s.delete(cid);
|
|
351
|
-
this.onDelete(
|
|
222
|
+
this.onDelete(cid, sid);
|
|
352
223
|
return true;
|
|
353
224
|
}
|
|
354
225
|
deleteByCid(cid, skipIf) {
|
|
@@ -361,30 +232,243 @@ var Bridge = class {
|
|
|
361
232
|
}
|
|
362
233
|
this.c2s.delete(cid);
|
|
363
234
|
this.s2c.delete(sid);
|
|
364
|
-
this.onDelete(
|
|
235
|
+
this.onDelete(cid, sid);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/class/TempMap.js
|
|
241
|
+
var TempMap = class extends Map {
|
|
242
|
+
constructor(ttl) {
|
|
243
|
+
super();
|
|
244
|
+
const _p = {
|
|
245
|
+
ttl: validInterval(ttl, true, "ttl"),
|
|
246
|
+
ts: /* @__PURE__ */ new Map()
|
|
247
|
+
};
|
|
248
|
+
_privs.set(this, _p);
|
|
249
|
+
}
|
|
250
|
+
set(key, value, overwrite = true) {
|
|
251
|
+
const { ts, ttl } = _privs.get(this);
|
|
252
|
+
if (!overwrite && this.has(key)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
this.delete(key);
|
|
256
|
+
super.set(key, value);
|
|
257
|
+
const t = setTimeout((_) => {
|
|
258
|
+
this.delete(key);
|
|
259
|
+
}, ttl);
|
|
260
|
+
ts.set(key, t);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
get(key, andDelete = false) {
|
|
264
|
+
const v = super.get(key);
|
|
265
|
+
if (andDelete) {
|
|
266
|
+
this.delete(key);
|
|
267
|
+
}
|
|
268
|
+
return v;
|
|
269
|
+
}
|
|
270
|
+
delete(key) {
|
|
271
|
+
if (!super.delete(key)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const { ts } = _privs.get(this);
|
|
275
|
+
const t = ts.get(key);
|
|
276
|
+
clearTimeout(t);
|
|
277
|
+
ts.delete(key);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/class/StoreGateway.js
|
|
283
|
+
import { solid } from "@randajan/props";
|
|
284
|
+
|
|
285
|
+
// src/stores/LiveStore.js
|
|
286
|
+
var LiveStore = class extends Map {
|
|
287
|
+
get(sid) {
|
|
288
|
+
return super.get(sid);
|
|
289
|
+
}
|
|
290
|
+
set(sid, state) {
|
|
291
|
+
super.set(sid, state);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
destroy(sid) {
|
|
295
|
+
return super.delete(sid);
|
|
296
|
+
}
|
|
297
|
+
list() {
|
|
298
|
+
return this.keys();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/class/StoreGateway.js
|
|
303
|
+
var formatState = (session2, maxAge, maxAgeDefault) => {
|
|
304
|
+
const ttl = maxAge ?? maxAgeDefault;
|
|
305
|
+
const expiresAt = Date.now() + ttl;
|
|
306
|
+
return { session: session2, expiresAt, ttl };
|
|
307
|
+
};
|
|
308
|
+
var requireList = (store) => {
|
|
309
|
+
if (!store.list) {
|
|
310
|
+
throw new TypeError(`${_errPrefix} store.list() is required`);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
var StoreGateway = class _StoreGateway {
|
|
314
|
+
static is(any) {
|
|
315
|
+
return any instanceof _StoreGateway;
|
|
316
|
+
}
|
|
317
|
+
constructor(opt = {}, emit = {}) {
|
|
318
|
+
const maxAge = validRange(opt.maxAge, ms.m(), ms.y(), false, "maxAge") ?? ms.M();
|
|
319
|
+
const autoCleanup = valid("boolean", opt.autoCleanup, false, "autoCleanup") ?? false;
|
|
320
|
+
solid(this, "emit", emit);
|
|
321
|
+
solid(this, "maxAge", maxAge);
|
|
322
|
+
solid(this, "store", validStore(opt.store, false) ?? new LiveStore());
|
|
323
|
+
if (autoCleanup) {
|
|
324
|
+
this.startAutoCleanup(validInterval(opt.autoCleanupMs, false, "autoCleanupMs"));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async list() {
|
|
328
|
+
const { store } = this;
|
|
329
|
+
requireList(store);
|
|
330
|
+
return store.list();
|
|
331
|
+
}
|
|
332
|
+
async get(sid) {
|
|
333
|
+
const { store } = this;
|
|
334
|
+
const c = await store.get(sid);
|
|
335
|
+
if (!c) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (Date.now() >= c.expiresAt) {
|
|
339
|
+
await this.destroy(sid);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
return JSON.parse(c.session);
|
|
344
|
+
} catch {
|
|
345
|
+
await this.destroy(sid);
|
|
346
|
+
throw new Error(`${_errPrefix} Invalid session JSON for sid='${sid}'.`);
|
|
347
|
+
}
|
|
348
|
+
;
|
|
349
|
+
}
|
|
350
|
+
async set(sid, session2, maxAge) {
|
|
351
|
+
const { store } = this;
|
|
352
|
+
const ses = validObject(session2, false, "session");
|
|
353
|
+
if (ses == null) {
|
|
354
|
+
return this.destroy(sid);
|
|
355
|
+
}
|
|
356
|
+
const to = JSON.stringify(ses);
|
|
357
|
+
const from = await store.get(sid, false);
|
|
358
|
+
if (to === from?.session) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
const isOk = await store.set(sid, formatState(to, maxAge, this.maxAge));
|
|
362
|
+
if (isOk === false) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
this.emit.notifySet(sid, !from);
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
async destroy(sid) {
|
|
369
|
+
const { store } = this;
|
|
370
|
+
const isOk = await store.destroy(sid);
|
|
371
|
+
if (isOk === false) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
this.emit.notifyDestroy(sid);
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
async cleanup() {
|
|
378
|
+
const { store } = this;
|
|
379
|
+
requireList(store);
|
|
380
|
+
const list = await store.list();
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
let cleared = 0;
|
|
383
|
+
await Promise.all([...list].map(async (sid) => {
|
|
384
|
+
const d = await store.get(sid);
|
|
385
|
+
if (!d) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (now < d.expiresAt) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (await this.destroy(sid)) {
|
|
392
|
+
cleared++;
|
|
393
|
+
}
|
|
394
|
+
}));
|
|
395
|
+
if (is("function", store.optimize)) {
|
|
396
|
+
await store.optimize(cleared);
|
|
397
|
+
}
|
|
398
|
+
this.emit.notifyCleanup(cleared);
|
|
399
|
+
return cleared;
|
|
400
|
+
}
|
|
401
|
+
startAutoCleanup(interval) {
|
|
402
|
+
const { store, maxAge, _intId } = this;
|
|
403
|
+
requireList(store);
|
|
404
|
+
const int = validRange(interval, ms.m(), ms.d(), false, "interval") ?? Math.max(ms.m(), Math.min(ms.d(), maxAge / 4));
|
|
405
|
+
clearInterval(_intId);
|
|
406
|
+
this._intId = setInterval((_) => this.cleanup().catch(() => {
|
|
407
|
+
}), int);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
stopAutoCleanup() {
|
|
411
|
+
const { _intId } = this;
|
|
412
|
+
if (!_intId) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
clearInterval(_intId);
|
|
416
|
+
delete this._intId;
|
|
365
417
|
return true;
|
|
366
418
|
}
|
|
367
419
|
};
|
|
368
420
|
|
|
369
421
|
// src/class/SessionBridge.js
|
|
370
|
-
var SessionBridge = class extends
|
|
422
|
+
var SessionBridge = class extends EventEmitter {
|
|
371
423
|
constructor(app, io, opt = {}) {
|
|
372
424
|
super();
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
425
|
+
const { appKeys, allowRndAppKeys, koaOpt, clientOpt, clientAlwaysRoll } = formatOptions(opt);
|
|
426
|
+
const _p = {};
|
|
427
|
+
ensureAppKeys(app, appKeys, allowRndAppKeys);
|
|
428
|
+
const tmp = new TempMap(5e3);
|
|
429
|
+
const brg = _p.brg = new Bridge({
|
|
430
|
+
onSet: (clientId, sessionId) => {
|
|
431
|
+
const isNew = !!tmp.get(sessionId, true);
|
|
432
|
+
this.emit("sessionSet", { clientId, sessionId, isNew, isInit: true });
|
|
433
|
+
},
|
|
434
|
+
onDelete: (clientId, sessionId) => this.emit("sessionDestroy", { clientId, sessionId })
|
|
381
435
|
});
|
|
382
|
-
|
|
383
|
-
|
|
436
|
+
_p.notifySet = (sid, isNew) => {
|
|
437
|
+
if (!sid) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const cid = brg.getBySid(sid);
|
|
441
|
+
if (!cid) {
|
|
442
|
+
tmp.set(sid, isNew, false);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
this.emit("sessionSet", { clientId: cid, sessionId: sid, isNew: !!isNew, isInit: false });
|
|
446
|
+
};
|
|
447
|
+
_p.notifyDestroy = (sid) => {
|
|
448
|
+
if (sid) {
|
|
449
|
+
brg.deleteBySid(sid);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
_p.notifyCleanup = (cleared) => {
|
|
453
|
+
this.emit("cleanup", cleared);
|
|
454
|
+
};
|
|
455
|
+
const gw = _p.gw = new StoreGateway(opt, _p);
|
|
456
|
+
const cc = createClientCookie(clientOpt);
|
|
457
|
+
const [koaSession, sc] = createKoaSession(app, gw, koaOpt, (ctx, sid) => {
|
|
384
458
|
const cid = cc.get(ctx);
|
|
385
459
|
brg.set(cid, sid);
|
|
386
460
|
});
|
|
387
|
-
const
|
|
461
|
+
const reviveCid = (ctx) => {
|
|
462
|
+
let cid = cc.get(ctx);
|
|
463
|
+
if (!cid) {
|
|
464
|
+
cc.set(ctx, cid = generateUid(24));
|
|
465
|
+
} else if (clientAlwaysRoll) {
|
|
466
|
+
cc.set(ctx, cid);
|
|
467
|
+
}
|
|
468
|
+
return cid;
|
|
469
|
+
};
|
|
470
|
+
const reviveSid = async (ctx, cid) => {
|
|
471
|
+
const reqSid = sc.get(ctx);
|
|
388
472
|
if (cid == null || reqSid == null) {
|
|
389
473
|
return;
|
|
390
474
|
}
|
|
@@ -398,21 +482,15 @@ var SessionBridge = class extends EventEmitter2 {
|
|
|
398
482
|
if (brg.getBySid(reqSid)) {
|
|
399
483
|
return;
|
|
400
484
|
}
|
|
401
|
-
if (!await
|
|
485
|
+
if (!await gw.get(reqSid)) {
|
|
402
486
|
return;
|
|
403
487
|
}
|
|
404
488
|
brg.set(cid, reqSid);
|
|
405
489
|
};
|
|
406
490
|
app.use(koaSession);
|
|
407
491
|
app.use(async (ctx, next) => {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (!cid) {
|
|
411
|
-
cc.set(ctx, cid = generateUid(24));
|
|
412
|
-
} else if (o.clientAlwaysRoll) {
|
|
413
|
-
cc.set(ctx, cid);
|
|
414
|
-
}
|
|
415
|
-
await regenerateSid(ctx, cid, sid);
|
|
492
|
+
const cid = reviveCid(ctx);
|
|
493
|
+
await reviveSid(ctx, cid);
|
|
416
494
|
solid2(ctx, "clientId", cid);
|
|
417
495
|
virtual(ctx, "sessionId", (_) => brg.getByCid(cid));
|
|
418
496
|
await next();
|
|
@@ -423,24 +501,87 @@ var SessionBridge = class extends EventEmitter2 {
|
|
|
423
501
|
const ctx = app.createContext(req, res);
|
|
424
502
|
await koaSession(ctx, async () => {
|
|
425
503
|
});
|
|
426
|
-
const cid =
|
|
427
|
-
|
|
428
|
-
await regenerateSid(ctx, cid, sid);
|
|
504
|
+
const cid = reviveCid(ctx);
|
|
505
|
+
await reviveSid(ctx, cid);
|
|
429
506
|
solid2(socket, "clientId", cid);
|
|
430
507
|
virtual(socket, "sessionId", (_) => brg.getByCid(cid));
|
|
431
508
|
solid2(socket, "withSession", async function(handler, onMissing) {
|
|
432
|
-
const onm = arguments.length > 1 ? onMissing : new Error(
|
|
433
|
-
return applySessionHandler(socket, handler,
|
|
509
|
+
const onm = arguments.length > 1 ? onMissing : new Error(`${_errPrefix} Session is missing for this socket.`);
|
|
510
|
+
return applySessionHandler(socket, handler, gw, onm);
|
|
434
511
|
}, false);
|
|
435
512
|
await next();
|
|
436
513
|
});
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
514
|
+
_privs.set(this, _p);
|
|
515
|
+
}
|
|
516
|
+
async getById(sessionId) {
|
|
517
|
+
const { gw, brg } = _privs.get(this);
|
|
518
|
+
const cid = brg.getBySid(sessionId);
|
|
519
|
+
if (cid) {
|
|
520
|
+
return gw.get(sessionId);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
async destroyById(sessionId) {
|
|
524
|
+
const { gw, brg } = _privs.get(this);
|
|
525
|
+
const cid = brg.getBySid(sessionId);
|
|
526
|
+
if (cid) {
|
|
527
|
+
return gw.destroy(sessionId);
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
async setById(sessionId, session2, maxAge) {
|
|
532
|
+
const { gw, brg } = _privs.get(this);
|
|
533
|
+
const cid = brg.getBySid(sessionId);
|
|
534
|
+
if (cid) {
|
|
535
|
+
return gw.set(sessionId, session2, maxAge);
|
|
536
|
+
}
|
|
537
|
+
throw new Error(`${_errPrefix} Creating session via setById() is prohibited. sessionId='${sessionId}'.`);
|
|
538
|
+
}
|
|
539
|
+
async getByClientId(clientId) {
|
|
540
|
+
const { gw, brg } = _privs.get(this);
|
|
541
|
+
const sid = brg.getByCid(clientId);
|
|
542
|
+
if (sid) {
|
|
543
|
+
return gw.get(sid);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async destroyByClientId(clientId) {
|
|
547
|
+
const { gw, brg } = _privs.get(this);
|
|
548
|
+
const sid = brg.getByCid(clientId);
|
|
549
|
+
if (sid) {
|
|
550
|
+
return gw.destroy(sid);
|
|
551
|
+
}
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
async setByClientId(clientId, session2, maxAge) {
|
|
555
|
+
const { gw, brg } = _privs.get(this);
|
|
556
|
+
const sid = brg.getByCid(clientId);
|
|
557
|
+
if (sid) {
|
|
558
|
+
return gw.set(sid, session2, maxAge);
|
|
559
|
+
}
|
|
560
|
+
throw new Error(`${_errPrefix} Creating session via setByClientId() is prohibited. clientId='${clientId}'.`);
|
|
561
|
+
}
|
|
562
|
+
getSessionId(clientId) {
|
|
563
|
+
return _privs.get(this).brg.getByCid(clientId);
|
|
564
|
+
}
|
|
565
|
+
getClientId(sessionId) {
|
|
566
|
+
return _privs.get(this).brg.getBySid(sessionId);
|
|
567
|
+
}
|
|
568
|
+
async cleanup() {
|
|
569
|
+
return _privs.get(this).gw.cleanup();
|
|
570
|
+
}
|
|
571
|
+
startAutoCleanup(interval) {
|
|
572
|
+
return _privs.get(this).gw.startAutoCleanup(interval);
|
|
573
|
+
}
|
|
574
|
+
stopAutoCleanup() {
|
|
575
|
+
return _privs.get(this).gw.stopAutoCleanup();
|
|
576
|
+
}
|
|
577
|
+
notifyStoreSet(sessionId, isNew) {
|
|
578
|
+
return _privs.get(this).notifySet(sessionId, isNew);
|
|
579
|
+
}
|
|
580
|
+
notifyStoreDestroy(sessionId) {
|
|
581
|
+
return _privs.get(this).notifyDestroy(sessionId);
|
|
582
|
+
}
|
|
583
|
+
notifyStoreCleanup(clearedCount) {
|
|
584
|
+
return _privs.get(this).notifyCleanup(clearedCount);
|
|
444
585
|
}
|
|
445
586
|
};
|
|
446
587
|
|
|
@@ -448,10 +589,11 @@ var SessionBridge = class extends EventEmitter2 {
|
|
|
448
589
|
var bridgeSession = (app, io, opt = {}) => new SessionBridge(app, io, opt);
|
|
449
590
|
var index_default = bridgeSession;
|
|
450
591
|
export {
|
|
592
|
+
LiveStore,
|
|
451
593
|
SessionBridge,
|
|
452
|
-
SessionStore,
|
|
453
594
|
bridgeSession,
|
|
454
595
|
index_default as default,
|
|
455
|
-
generateUid
|
|
596
|
+
generateUid,
|
|
597
|
+
ms
|
|
456
598
|
};
|
|
457
599
|
//# sourceMappingURL=index.js.map
|