@randajan/koa-io-session 2.2.0 → 3.0.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/README.md +158 -104
- package/dist/cjs/index.cjs +394 -204
- 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-27XQ42ES.js +98 -0
- package/dist/esm/chunk-27XQ42ES.js.map +7 -0
- package/dist/esm/index.mjs +364 -246
- 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,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
_customOptKeys,
|
|
3
|
+
_errPrefix,
|
|
4
|
+
_privs,
|
|
5
|
+
generateUid,
|
|
6
|
+
is,
|
|
7
|
+
ms,
|
|
8
|
+
valid,
|
|
9
|
+
validInterval,
|
|
10
|
+
validObject,
|
|
11
|
+
validRange,
|
|
12
|
+
validStore
|
|
13
|
+
} from "./chunk-27XQ42ES.js";
|
|
14
|
+
|
|
1
15
|
// src/class/SessionBridge.js
|
|
2
|
-
import { EventEmitter
|
|
16
|
+
import { EventEmitter } from "events";
|
|
3
17
|
import { ServerResponse } from "http";
|
|
4
18
|
import { solid as solid2, virtual } from "@randajan/props";
|
|
5
19
|
|
|
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
20
|
// src/httpSession.js
|
|
69
21
|
import session from "koa-session";
|
|
70
22
|
|
|
@@ -88,10 +40,11 @@ var wrapExternalKey = (opt, onSet) => {
|
|
|
88
40
|
};
|
|
89
41
|
|
|
90
42
|
// src/httpSession.js
|
|
91
|
-
var createKoaSession = (
|
|
92
|
-
const
|
|
43
|
+
var createKoaSession = (app, gw, opt, onSet) => {
|
|
44
|
+
const maxAge = gw.maxAge;
|
|
45
|
+
const store = wrapStore(gw);
|
|
93
46
|
const externalKey = wrapExternalKey(opt, onSet);
|
|
94
|
-
const koaSession = session({ ...opt, store, externalKey }, app);
|
|
47
|
+
const koaSession = session({ ...opt, maxAge, store, externalKey }, app);
|
|
95
48
|
return [koaSession, externalKey];
|
|
96
49
|
};
|
|
97
50
|
var createClientCookie = (opt) => {
|
|
@@ -111,41 +64,6 @@ var createClientCookie = (opt) => {
|
|
|
111
64
|
// src/socketSession.js
|
|
112
65
|
import { solids } from "@randajan/props";
|
|
113
66
|
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
67
|
var applyOnMissing = (onMissing) => {
|
|
150
68
|
if (onMissing instanceof Error) {
|
|
151
69
|
throw onMissing;
|
|
@@ -155,126 +73,55 @@ var applyOnMissing = (onMissing) => {
|
|
|
155
73
|
}
|
|
156
74
|
return onMissing;
|
|
157
75
|
};
|
|
158
|
-
var runSessionHandler = async (socket, handler,
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
if (!current) {
|
|
76
|
+
var runSessionHandler = async (sid, socket, handler, gw, onMissing) => {
|
|
77
|
+
const session2 = await gw.get(sid);
|
|
78
|
+
if (!session2) {
|
|
162
79
|
return applyOnMissing(onMissing);
|
|
163
80
|
}
|
|
164
|
-
const
|
|
165
|
-
const sessionCtx = createSessionCtx(sid, session2, socket);
|
|
166
|
-
const originalHash = createSessionHash(sessionCtx.session);
|
|
81
|
+
const sessionCtx = solids({ session: session2 }, { sessionId: sid, socket });
|
|
167
82
|
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);
|
|
83
|
+
if (sid === socket.sessionId) {
|
|
84
|
+
await gw.set(sid, sessionCtx.session);
|
|
175
85
|
}
|
|
176
86
|
return result;
|
|
177
87
|
};
|
|
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;
|
|
237
|
-
}
|
|
238
|
-
if (Date.now() < d.expiresAt) {
|
|
239
|
-
return d.session;
|
|
88
|
+
var createLock = (sid) => {
|
|
89
|
+
let _release;
|
|
90
|
+
const previous = sidLocks.get(sid);
|
|
91
|
+
const current = new Promise((resolve) => {
|
|
92
|
+
_release = resolve;
|
|
93
|
+
});
|
|
94
|
+
sidLocks.set(sid, current);
|
|
95
|
+
const release = () => {
|
|
96
|
+
_release();
|
|
97
|
+
if (sidLocks.get(sid) === current) {
|
|
98
|
+
sidLocks.delete(sid);
|
|
240
99
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
100
|
+
};
|
|
101
|
+
return [previous, release];
|
|
102
|
+
};
|
|
103
|
+
var applySessionHandler = async (socket, handler, gw, onMissing) => {
|
|
104
|
+
valid("function", handler, true, "handler");
|
|
105
|
+
for (let i = 0; i < 5; i++) {
|
|
106
|
+
const sid = socket.sessionId;
|
|
107
|
+
if (!sid) {
|
|
108
|
+
return applyOnMissing(onMissing);
|
|
247
109
|
}
|
|
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);
|
|
110
|
+
const [previous, release] = createLock(sid);
|
|
111
|
+
if (previous) {
|
|
112
|
+
await previous;
|
|
259
113
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
}
|
|
114
|
+
if (previous && sid !== socket.sessionId) {
|
|
115
|
+
release();
|
|
116
|
+
continue;
|
|
272
117
|
}
|
|
273
|
-
|
|
274
|
-
|
|
118
|
+
try {
|
|
119
|
+
return await runSessionHandler(sid, socket, handler, gw, onMissing);
|
|
120
|
+
} finally {
|
|
121
|
+
release();
|
|
275
122
|
}
|
|
276
|
-
return cleared;
|
|
277
123
|
}
|
|
124
|
+
throw new Error(`${_errPrefix} socket.sessionId changed during withSession execution.`);
|
|
278
125
|
};
|
|
279
126
|
|
|
280
127
|
// src/formatOptions.js
|
|
@@ -287,9 +134,7 @@ var pickKoaOpt = (rawOpt) => {
|
|
|
287
134
|
koaOpt[key] = rawOpt[key];
|
|
288
135
|
}
|
|
289
136
|
koaOpt.key = valid("string", koaOpt.key, false, "key") ?? generateUid(12);
|
|
290
|
-
koaOpt.maxAge = validRange(ms.s(), ms.y(), koaOpt.maxAge, false, "maxAge") ?? ms.M();
|
|
291
137
|
koaOpt.signed = valid("boolean", koaOpt.signed, false, "signed") ?? true;
|
|
292
|
-
koaOpt.store = validStore(rawOpt.store || new SessionStore(rawOpt));
|
|
293
138
|
return koaOpt;
|
|
294
139
|
};
|
|
295
140
|
var formatOptions = (opt = {}) => {
|
|
@@ -329,7 +174,7 @@ var Bridge = class {
|
|
|
329
174
|
}
|
|
330
175
|
this.c2s.set(cid, sid);
|
|
331
176
|
this.s2c.set(sid, cid);
|
|
332
|
-
this.onSet(
|
|
177
|
+
this.onSet(cid, sid);
|
|
333
178
|
return true;
|
|
334
179
|
}
|
|
335
180
|
getByCid(cid) {
|
|
@@ -348,7 +193,7 @@ var Bridge = class {
|
|
|
348
193
|
}
|
|
349
194
|
this.s2c.delete(sid);
|
|
350
195
|
this.c2s.delete(cid);
|
|
351
|
-
this.onDelete(
|
|
196
|
+
this.onDelete(cid, sid);
|
|
352
197
|
return true;
|
|
353
198
|
}
|
|
354
199
|
deleteByCid(cid, skipIf) {
|
|
@@ -361,30 +206,245 @@ var Bridge = class {
|
|
|
361
206
|
}
|
|
362
207
|
this.c2s.delete(cid);
|
|
363
208
|
this.s2c.delete(sid);
|
|
364
|
-
this.onDelete(
|
|
209
|
+
this.onDelete(cid, sid);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/class/TempMap.js
|
|
215
|
+
var TempMap = class extends Map {
|
|
216
|
+
constructor(ttl) {
|
|
217
|
+
super();
|
|
218
|
+
const _p = {
|
|
219
|
+
ttl: validInterval(ttl, true, "ttl"),
|
|
220
|
+
ts: /* @__PURE__ */ new Map()
|
|
221
|
+
};
|
|
222
|
+
_privs.set(this, _p);
|
|
223
|
+
}
|
|
224
|
+
set(key, value, overwrite = true) {
|
|
225
|
+
const { ts, ttl } = _privs.get(this);
|
|
226
|
+
if (!overwrite && this.has(key)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
this.delete(key);
|
|
230
|
+
super.set(key, value);
|
|
231
|
+
const t = setTimeout((_) => {
|
|
232
|
+
this.delete(key);
|
|
233
|
+
}, ttl);
|
|
234
|
+
ts.set(key, t);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
get(key, andDelete = false) {
|
|
238
|
+
const v = super.get(key);
|
|
239
|
+
if (andDelete) {
|
|
240
|
+
this.delete(key);
|
|
241
|
+
}
|
|
242
|
+
return v;
|
|
243
|
+
}
|
|
244
|
+
delete(key) {
|
|
245
|
+
if (!super.delete(key)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const { ts } = _privs.get(this);
|
|
249
|
+
const t = ts.get(key);
|
|
250
|
+
clearTimeout(t);
|
|
251
|
+
ts.delete(key);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/class/StoreGateway.js
|
|
257
|
+
import { solid } from "@randajan/props";
|
|
258
|
+
|
|
259
|
+
// src/stores/LiveStore.js
|
|
260
|
+
var LiveStore = class extends Map {
|
|
261
|
+
get(sid) {
|
|
262
|
+
return super.get(sid);
|
|
263
|
+
}
|
|
264
|
+
set(sid, state) {
|
|
265
|
+
super.set(sid, state);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
destroy(sid) {
|
|
269
|
+
return super.delete(sid);
|
|
270
|
+
}
|
|
271
|
+
list() {
|
|
272
|
+
return this.keys();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// src/class/StoreGateway.js
|
|
277
|
+
var formatState = (session2, maxAge, maxAgeDefault) => {
|
|
278
|
+
const ttl = maxAge ?? maxAgeDefault;
|
|
279
|
+
const expiresAt = Date.now() + ttl;
|
|
280
|
+
return { session: session2, expiresAt, ttl };
|
|
281
|
+
};
|
|
282
|
+
var requireList = (store) => {
|
|
283
|
+
if (!store.list) {
|
|
284
|
+
throw new TypeError(`${_errPrefix} store.list() is required`);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var StoreGateway = class _StoreGateway {
|
|
288
|
+
static is(any) {
|
|
289
|
+
return any instanceof _StoreGateway;
|
|
290
|
+
}
|
|
291
|
+
constructor(opt = {}, emit = {}) {
|
|
292
|
+
const maxAge = validRange(opt.maxAge, ms.m(), ms.y(), false, "maxAge") ?? ms.M();
|
|
293
|
+
const autoCleanup = valid("boolean", opt.autoCleanup, false, "autoCleanup") ?? false;
|
|
294
|
+
solid(this, "emit", emit);
|
|
295
|
+
solid(this, "maxAge", maxAge);
|
|
296
|
+
solid(this, "store", validStore(opt.store, false) ?? new LiveStore());
|
|
297
|
+
if (autoCleanup) {
|
|
298
|
+
this.startAutoCleanup(validInterval(opt.autoCleanupMs, false, "autoCleanupMs"));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async list() {
|
|
302
|
+
const { store } = this;
|
|
303
|
+
requireList(store);
|
|
304
|
+
return store.list();
|
|
305
|
+
}
|
|
306
|
+
async get(sid) {
|
|
307
|
+
const { store } = this;
|
|
308
|
+
const c = await store.get(sid);
|
|
309
|
+
if (!c) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (Date.now() >= c.expiresAt) {
|
|
313
|
+
await this.destroy(sid);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
return JSON.parse(c.session);
|
|
318
|
+
} catch {
|
|
319
|
+
await this.destroy(sid);
|
|
320
|
+
throw new Error(`${_errPrefix} Invalid session JSON for sid='${sid}'.`);
|
|
321
|
+
}
|
|
322
|
+
;
|
|
323
|
+
}
|
|
324
|
+
async set(sid, session2, maxAge) {
|
|
325
|
+
const { store } = this;
|
|
326
|
+
const ses = validObject(session2, false, "session");
|
|
327
|
+
if (ses == null) {
|
|
328
|
+
return this.destroy(sid);
|
|
329
|
+
}
|
|
330
|
+
const to = JSON.stringify(ses);
|
|
331
|
+
const from = await store.get(sid, false);
|
|
332
|
+
if (to === from?.session) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
const isOk = await store.set(sid, formatState(to, maxAge, this.maxAge));
|
|
336
|
+
if (isOk === false) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
this.emit.notifySet(sid, !from);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
async destroy(sid) {
|
|
343
|
+
const { store } = this;
|
|
344
|
+
const isOk = await store.destroy(sid);
|
|
345
|
+
if (isOk === false) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
this.emit.notifyDestroy(sid);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
async cleanup() {
|
|
352
|
+
const { store } = this;
|
|
353
|
+
requireList(store);
|
|
354
|
+
const list = await store.list();
|
|
355
|
+
const now = Date.now();
|
|
356
|
+
let cleared = 0;
|
|
357
|
+
await Promise.all([...list].map(async (sid) => {
|
|
358
|
+
const d = await store.get(sid);
|
|
359
|
+
if (!d) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (now < d.expiresAt) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (await this.destroy(sid)) {
|
|
366
|
+
cleared++;
|
|
367
|
+
}
|
|
368
|
+
}));
|
|
369
|
+
if (is("function", store.optimize)) {
|
|
370
|
+
await store.optimize(cleared);
|
|
371
|
+
}
|
|
372
|
+
this.emit.notifyCleanup(cleared);
|
|
373
|
+
return cleared;
|
|
374
|
+
}
|
|
375
|
+
startAutoCleanup(interval) {
|
|
376
|
+
const { store, maxAge, _intId } = this;
|
|
377
|
+
requireList(store);
|
|
378
|
+
const int = validRange(interval, ms.m(), ms.d(), false, "interval") ?? Math.max(ms.m(), Math.min(ms.d(), maxAge / 4));
|
|
379
|
+
clearInterval(_intId);
|
|
380
|
+
this._intId = setInterval((_) => this.cleanup().catch(() => {
|
|
381
|
+
}), int);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
stopAutoCleanup() {
|
|
385
|
+
const { _intId } = this;
|
|
386
|
+
if (!_intId) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
clearInterval(_intId);
|
|
390
|
+
delete this._intId;
|
|
365
391
|
return true;
|
|
366
392
|
}
|
|
367
393
|
};
|
|
368
394
|
|
|
369
395
|
// src/class/SessionBridge.js
|
|
370
|
-
var SessionBridge = class extends
|
|
396
|
+
var SessionBridge = class extends EventEmitter {
|
|
371
397
|
constructor(app, io, opt = {}) {
|
|
372
398
|
super();
|
|
373
399
|
if (!app.keys) {
|
|
374
400
|
app.keys = Array(6).fill().map(() => generateUid(12));
|
|
375
401
|
}
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
402
|
+
const { koaOpt, clientOpt, clientAlwaysRoll } = formatOptions(opt);
|
|
403
|
+
const _p = {};
|
|
404
|
+
const tmp = new TempMap(5e3);
|
|
405
|
+
const brg = _p.brg = new Bridge({
|
|
406
|
+
onSet: (clientId, sessionId) => {
|
|
407
|
+
const isNew = !!tmp.get(sessionId, true);
|
|
408
|
+
this.emit("sessionSet", { clientId, sessionId, isNew, isInit: true });
|
|
409
|
+
},
|
|
410
|
+
onDelete: (clientId, sessionId) => this.emit("sessionDestroy", { clientId, sessionId })
|
|
381
411
|
});
|
|
382
|
-
|
|
383
|
-
|
|
412
|
+
_p.notifySet = (sid, isNew) => {
|
|
413
|
+
if (!sid) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const cid = brg.getBySid(sid);
|
|
417
|
+
if (!cid) {
|
|
418
|
+
tmp.set(sid, isNew, false);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
this.emit("sessionSet", { clientId: cid, sessionId: sid, isNew: !!isNew, isInit: false });
|
|
422
|
+
};
|
|
423
|
+
_p.notifyDestroy = (sid) => {
|
|
424
|
+
if (sid) {
|
|
425
|
+
brg.deleteBySid(sid);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
_p.notifyCleanup = (cleared) => {
|
|
429
|
+
this.emit("cleanup", cleared);
|
|
430
|
+
};
|
|
431
|
+
const gw = _p.gw = new StoreGateway(opt, _p);
|
|
432
|
+
const cc = createClientCookie(clientOpt);
|
|
433
|
+
const [koaSession, sc] = createKoaSession(app, gw, koaOpt, (ctx, sid) => {
|
|
384
434
|
const cid = cc.get(ctx);
|
|
385
435
|
brg.set(cid, sid);
|
|
386
436
|
});
|
|
387
|
-
const
|
|
437
|
+
const reviveCid = (ctx) => {
|
|
438
|
+
let cid = cc.get(ctx);
|
|
439
|
+
if (!cid) {
|
|
440
|
+
cc.set(ctx, cid = generateUid(24));
|
|
441
|
+
} else if (clientAlwaysRoll) {
|
|
442
|
+
cc.set(ctx, cid);
|
|
443
|
+
}
|
|
444
|
+
return cid;
|
|
445
|
+
};
|
|
446
|
+
const reviveSid = async (ctx, cid) => {
|
|
447
|
+
const reqSid = sc.get(ctx);
|
|
388
448
|
if (cid == null || reqSid == null) {
|
|
389
449
|
return;
|
|
390
450
|
}
|
|
@@ -398,21 +458,15 @@ var SessionBridge = class extends EventEmitter2 {
|
|
|
398
458
|
if (brg.getBySid(reqSid)) {
|
|
399
459
|
return;
|
|
400
460
|
}
|
|
401
|
-
if (!await
|
|
461
|
+
if (!await gw.get(reqSid)) {
|
|
402
462
|
return;
|
|
403
463
|
}
|
|
404
464
|
brg.set(cid, reqSid);
|
|
405
465
|
};
|
|
406
466
|
app.use(koaSession);
|
|
407
467
|
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);
|
|
468
|
+
const cid = reviveCid(ctx);
|
|
469
|
+
await reviveSid(ctx, cid);
|
|
416
470
|
solid2(ctx, "clientId", cid);
|
|
417
471
|
virtual(ctx, "sessionId", (_) => brg.getByCid(cid));
|
|
418
472
|
await next();
|
|
@@ -423,24 +477,87 @@ var SessionBridge = class extends EventEmitter2 {
|
|
|
423
477
|
const ctx = app.createContext(req, res);
|
|
424
478
|
await koaSession(ctx, async () => {
|
|
425
479
|
});
|
|
426
|
-
const cid =
|
|
427
|
-
|
|
428
|
-
await regenerateSid(ctx, cid, sid);
|
|
480
|
+
const cid = reviveCid(ctx);
|
|
481
|
+
await reviveSid(ctx, cid);
|
|
429
482
|
solid2(socket, "clientId", cid);
|
|
430
483
|
virtual(socket, "sessionId", (_) => brg.getByCid(cid));
|
|
431
484
|
solid2(socket, "withSession", async function(handler, onMissing) {
|
|
432
|
-
const onm = arguments.length > 1 ? onMissing : new Error(
|
|
433
|
-
return applySessionHandler(socket, handler,
|
|
485
|
+
const onm = arguments.length > 1 ? onMissing : new Error(`${_errPrefix} Session is missing for this socket.`);
|
|
486
|
+
return applySessionHandler(socket, handler, gw, onm);
|
|
434
487
|
}, false);
|
|
435
488
|
await next();
|
|
436
489
|
});
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
490
|
+
_privs.set(this, _p);
|
|
491
|
+
}
|
|
492
|
+
async getById(sessionId) {
|
|
493
|
+
const { gw, brg } = _privs.get(this);
|
|
494
|
+
const cid = brg.getBySid(sessionId);
|
|
495
|
+
if (cid) {
|
|
496
|
+
return gw.get(sessionId);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async destroyById(sessionId) {
|
|
500
|
+
const { gw, brg } = _privs.get(this);
|
|
501
|
+
const cid = brg.getBySid(sessionId);
|
|
502
|
+
if (cid) {
|
|
503
|
+
return gw.destroy(sessionId);
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
async setById(sessionId, session2, maxAge) {
|
|
508
|
+
const { gw, brg } = _privs.get(this);
|
|
509
|
+
const cid = brg.getBySid(sessionId);
|
|
510
|
+
if (cid) {
|
|
511
|
+
return gw.set(sessionId, session2, maxAge);
|
|
512
|
+
}
|
|
513
|
+
throw new Error(`${_errPrefix} Creating session via setById() is prohibited. sessionId='${sessionId}'.`);
|
|
514
|
+
}
|
|
515
|
+
async getByClientId(clientId) {
|
|
516
|
+
const { gw, brg } = _privs.get(this);
|
|
517
|
+
const sid = brg.getByCid(clientId);
|
|
518
|
+
if (sid) {
|
|
519
|
+
return gw.get(sid);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async destroyByClientId(clientId) {
|
|
523
|
+
const { gw, brg } = _privs.get(this);
|
|
524
|
+
const sid = brg.getByCid(clientId);
|
|
525
|
+
if (sid) {
|
|
526
|
+
return gw.destroy(sid);
|
|
527
|
+
}
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
async setByClientId(clientId, session2, maxAge) {
|
|
531
|
+
const { gw, brg } = _privs.get(this);
|
|
532
|
+
const sid = brg.getByCid(clientId);
|
|
533
|
+
if (sid) {
|
|
534
|
+
return gw.set(sid, session2, maxAge);
|
|
535
|
+
}
|
|
536
|
+
throw new Error(`${_errPrefix} Creating session via setByClientId() is prohibited. clientId='${clientId}'.`);
|
|
537
|
+
}
|
|
538
|
+
getSessionId(clientId) {
|
|
539
|
+
return _privs.get(this).brg.getByCid(clientId);
|
|
540
|
+
}
|
|
541
|
+
getClientId(sessionId) {
|
|
542
|
+
return _privs.get(this).brg.getBySid(sessionId);
|
|
543
|
+
}
|
|
544
|
+
async cleanup() {
|
|
545
|
+
return _privs.get(this).gw.cleanup();
|
|
546
|
+
}
|
|
547
|
+
startAutoCleanup(interval) {
|
|
548
|
+
return _privs.get(this).gw.startAutoCleanup(interval);
|
|
549
|
+
}
|
|
550
|
+
stopAutoCleanup() {
|
|
551
|
+
return _privs.get(this).gw.stopAutoCleanup();
|
|
552
|
+
}
|
|
553
|
+
notifyStoreSet(sessionId, isNew) {
|
|
554
|
+
return _privs.get(this).notifySet(sessionId, isNew);
|
|
555
|
+
}
|
|
556
|
+
notifyStoreDestroy(sessionId) {
|
|
557
|
+
return _privs.get(this).notifyDestroy(sessionId);
|
|
558
|
+
}
|
|
559
|
+
notifyStoreCleanup(clearedCount) {
|
|
560
|
+
return _privs.get(this).notifyCleanup(clearedCount);
|
|
444
561
|
}
|
|
445
562
|
};
|
|
446
563
|
|
|
@@ -448,10 +565,11 @@ var SessionBridge = class extends EventEmitter2 {
|
|
|
448
565
|
var bridgeSession = (app, io, opt = {}) => new SessionBridge(app, io, opt);
|
|
449
566
|
var index_default = bridgeSession;
|
|
450
567
|
export {
|
|
568
|
+
LiveStore,
|
|
451
569
|
SessionBridge,
|
|
452
|
-
SessionStore,
|
|
453
570
|
bridgeSession,
|
|
454
571
|
index_default as default,
|
|
455
|
-
generateUid
|
|
572
|
+
generateUid,
|
|
573
|
+
ms
|
|
456
574
|
};
|
|
457
575
|
//# sourceMappingURL=index.js.map
|