@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/cjs/index.cjs
CHANGED
|
@@ -29,50 +29,73 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
// src/index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
LiveStore: () => LiveStore,
|
|
32
33
|
SessionBridge: () => SessionBridge,
|
|
33
|
-
SessionStore: () => SessionStore,
|
|
34
34
|
bridgeSession: () => bridgeSession,
|
|
35
35
|
default: () => index_default,
|
|
36
|
-
generateUid: () => generateUid
|
|
36
|
+
generateUid: () => generateUid,
|
|
37
|
+
ms: () => ms
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(index_exports);
|
|
39
40
|
|
|
40
41
|
// src/class/SessionBridge.js
|
|
41
|
-
var
|
|
42
|
+
var import_events = require("events");
|
|
42
43
|
var import_http = require("http");
|
|
43
44
|
var import_props4 = require("@randajan/props");
|
|
44
45
|
|
|
45
46
|
// src/tools.js
|
|
46
47
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
48
|
+
|
|
49
|
+
// src/const.js
|
|
50
|
+
var _errPrefix = "[koa-io-session]";
|
|
51
|
+
var ms = {
|
|
52
|
+
s: (v = 1) => v * 1e3,
|
|
53
|
+
m: (v = 1) => ms.s(v * 60),
|
|
54
|
+
h: (v = 1) => ms.m(v * 60),
|
|
55
|
+
d: (v = 1) => ms.h(v * 24),
|
|
56
|
+
w: (v = 1) => ms.d(v * 7),
|
|
57
|
+
M: (v = 1) => ms.d(v * 30),
|
|
58
|
+
y: (v = 1) => ms.d(v * 365)
|
|
59
|
+
};
|
|
60
|
+
var _customOptKeys = /* @__PURE__ */ new Set([
|
|
61
|
+
"store",
|
|
62
|
+
"autoCleanup",
|
|
63
|
+
"autoCleanupMs",
|
|
64
|
+
"clientKey",
|
|
65
|
+
"clientMaxAge",
|
|
66
|
+
"clientAlwaysRoll"
|
|
67
|
+
]);
|
|
68
|
+
var _privs = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
|
|
70
|
+
// src/tools.js
|
|
47
71
|
var generateUid = (len = 16) => import_crypto.default.randomBytes(len).toString("base64url").slice(0, len);
|
|
48
72
|
var is = (type, any) => typeof any === type;
|
|
73
|
+
var _typeOf = (any) => any === null ? "null" : Array.isArray(any) ? "array" : typeof any;
|
|
74
|
+
var _err = (msg) => `${_errPrefix} ${msg}`;
|
|
49
75
|
var valid = (type, any, req = false, msg = "argument") => {
|
|
50
76
|
if (any == null) {
|
|
51
77
|
if (!req) {
|
|
52
78
|
return;
|
|
53
79
|
}
|
|
54
|
-
throw new
|
|
80
|
+
throw new TypeError(_err(`Missing required '${msg}'. Expected type '${type}'.`));
|
|
55
81
|
}
|
|
56
82
|
if (is(type, any)) {
|
|
57
83
|
return any;
|
|
58
84
|
}
|
|
59
|
-
throw new
|
|
85
|
+
throw new TypeError(_err(`Invalid '${msg}'. Expected type '${type}', received '${_typeOf(any)}'.`));
|
|
60
86
|
};
|
|
61
|
-
var validRange = (min, max,
|
|
87
|
+
var validRange = (any, min, max, req = false, msg = "argument") => {
|
|
62
88
|
const num = valid("number", any, req, msg);
|
|
63
89
|
if (num == null) {
|
|
64
90
|
return;
|
|
65
91
|
}
|
|
66
|
-
if (num < min) {
|
|
67
|
-
throw new
|
|
68
|
-
}
|
|
69
|
-
if (num > max) {
|
|
70
|
-
throw new Error(`${msg} must be less than ${max}`);
|
|
92
|
+
if (num < min || num > max) {
|
|
93
|
+
throw new RangeError(_err(`Invalid '${msg}'. Expected value in range <${min}, ${max}>, received '${num}'.`));
|
|
71
94
|
}
|
|
72
95
|
return num;
|
|
73
96
|
};
|
|
74
97
|
var validInterval = (any, req = false, msg = "argument") => {
|
|
75
|
-
return validRange(10, 2147483647,
|
|
98
|
+
return validRange(any, 10, 2147483647, req, msg);
|
|
76
99
|
};
|
|
77
100
|
var validObject = (any, req = false, msg = "argument") => {
|
|
78
101
|
const obj = valid("object", any, req, msg);
|
|
@@ -82,25 +105,27 @@ var validObject = (any, req = false, msg = "argument") => {
|
|
|
82
105
|
if (!Array.isArray(obj)) {
|
|
83
106
|
return obj;
|
|
84
107
|
}
|
|
85
|
-
throw new
|
|
108
|
+
throw new TypeError(_err(`Invalid '${msg}'. Expected plain object, received 'array'.`));
|
|
86
109
|
};
|
|
87
|
-
var validStore = (store) => {
|
|
110
|
+
var validStore = (store, req = false) => {
|
|
111
|
+
store = validObject(store, req, "store");
|
|
112
|
+
if (!store) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
88
115
|
const missing = [];
|
|
89
|
-
if (!is("function", store
|
|
116
|
+
if (!is("function", store.get)) {
|
|
90
117
|
missing.push("get()");
|
|
91
118
|
}
|
|
92
|
-
if (!is("function", store
|
|
119
|
+
if (!is("function", store.set)) {
|
|
93
120
|
missing.push("set()");
|
|
94
121
|
}
|
|
95
|
-
if (!is("function", store
|
|
122
|
+
if (!is("function", store.destroy)) {
|
|
96
123
|
missing.push("destroy()");
|
|
97
124
|
}
|
|
98
|
-
if (!is("function", store?.on)) {
|
|
99
|
-
missing.push("on()");
|
|
100
|
-
}
|
|
101
125
|
if (missing.length) {
|
|
102
|
-
throw new TypeError(`store
|
|
126
|
+
throw new TypeError(_err(`Invalid 'store'. Missing required API: ${missing.join(", ")}.`));
|
|
103
127
|
}
|
|
128
|
+
valid("function", store.list, false, "store.list()");
|
|
104
129
|
return store;
|
|
105
130
|
};
|
|
106
131
|
|
|
@@ -127,10 +152,11 @@ var wrapExternalKey = (opt, onSet) => {
|
|
|
127
152
|
};
|
|
128
153
|
|
|
129
154
|
// src/httpSession.js
|
|
130
|
-
var createKoaSession = (
|
|
131
|
-
const
|
|
155
|
+
var createKoaSession = (app, gw, opt, onSet) => {
|
|
156
|
+
const maxAge = gw.maxAge;
|
|
157
|
+
const store = wrapStore(gw);
|
|
132
158
|
const externalKey = wrapExternalKey(opt, onSet);
|
|
133
|
-
const koaSession = (0, import_koa_session.default)({ ...opt, store, externalKey }, app);
|
|
159
|
+
const koaSession = (0, import_koa_session.default)({ ...opt, maxAge, store, externalKey }, app);
|
|
134
160
|
return [koaSession, externalKey];
|
|
135
161
|
};
|
|
136
162
|
var createClientCookie = (opt) => {
|
|
@@ -150,41 +176,6 @@ var createClientCookie = (opt) => {
|
|
|
150
176
|
// src/socketSession.js
|
|
151
177
|
var import_props = require("@randajan/props");
|
|
152
178
|
var sidLocks = /* @__PURE__ */ new Map();
|
|
153
|
-
var createSessionCtx = (sessionId, session2, socket) => (0, import_props.solids)({ session: session2 }, { sessionId, socket });
|
|
154
|
-
var createSessionHash = (session2) => {
|
|
155
|
-
try {
|
|
156
|
-
return JSON.stringify(session2 ?? null);
|
|
157
|
-
} catch {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
var isSessionHashChanged = (originalHash, session2) => {
|
|
162
|
-
const nextHash = createSessionHash(session2);
|
|
163
|
-
if (originalHash == null || nextHash == null) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
return originalHash !== nextHash;
|
|
167
|
-
};
|
|
168
|
-
var withLock = async (task, socket, ...args) => {
|
|
169
|
-
const sid = socket.sessionId;
|
|
170
|
-
const previous = sidLocks.get(sid);
|
|
171
|
-
let releaseCurrent;
|
|
172
|
-
const current = new Promise((resolve) => {
|
|
173
|
-
releaseCurrent = resolve;
|
|
174
|
-
});
|
|
175
|
-
sidLocks.set(sid, current);
|
|
176
|
-
if (previous) {
|
|
177
|
-
await previous;
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
return await task(socket, ...args);
|
|
181
|
-
} finally {
|
|
182
|
-
releaseCurrent();
|
|
183
|
-
if (sidLocks.get(sid) === current) {
|
|
184
|
-
sidLocks.delete(sid);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
179
|
var applyOnMissing = (onMissing) => {
|
|
189
180
|
if (onMissing instanceof Error) {
|
|
190
181
|
throw onMissing;
|
|
@@ -194,126 +185,55 @@ var applyOnMissing = (onMissing) => {
|
|
|
194
185
|
}
|
|
195
186
|
return onMissing;
|
|
196
187
|
};
|
|
197
|
-
var runSessionHandler = async (socket, handler,
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
if (!current) {
|
|
188
|
+
var runSessionHandler = async (sid, socket, handler, gw, onMissing) => {
|
|
189
|
+
const session2 = await gw.get(sid);
|
|
190
|
+
if (!session2) {
|
|
201
191
|
return applyOnMissing(onMissing);
|
|
202
192
|
}
|
|
203
|
-
const
|
|
204
|
-
const sessionCtx = createSessionCtx(sid, session2, socket);
|
|
205
|
-
const originalHash = createSessionHash(sessionCtx.session);
|
|
193
|
+
const sessionCtx = (0, import_props.solids)({ session: session2 }, { sessionId: sid, socket });
|
|
206
194
|
const result = await handler(sessionCtx, socket);
|
|
207
|
-
if (
|
|
208
|
-
await
|
|
209
|
-
return result;
|
|
210
|
-
}
|
|
211
|
-
sessionCtx.session = validObject(sessionCtx.session, false, "session");
|
|
212
|
-
if (isSessionHashChanged(originalHash, sessionCtx.session)) {
|
|
213
|
-
await store.set(sid, sessionCtx.session);
|
|
195
|
+
if (sid === socket.sessionId) {
|
|
196
|
+
await gw.set(sid, sessionCtx.session);
|
|
214
197
|
}
|
|
215
198
|
return result;
|
|
216
199
|
};
|
|
217
|
-
var
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
var import_props2 = require("@randajan/props");
|
|
229
|
-
var import_events = require("events");
|
|
230
|
-
|
|
231
|
-
// src/const.js
|
|
232
|
-
var ms = {
|
|
233
|
-
s: (v = 1) => v * 1e3,
|
|
234
|
-
m: (v = 1) => ms.s(v * 60),
|
|
235
|
-
h: (v = 1) => ms.m(v * 60),
|
|
236
|
-
d: (v = 1) => ms.h(v * 24),
|
|
237
|
-
w: (v = 1) => ms.d(v * 7),
|
|
238
|
-
M: (v = 1) => ms.d(v * 30),
|
|
239
|
-
y: (v = 1) => ms.d(v * 365)
|
|
240
|
-
};
|
|
241
|
-
var _customOptKeys = /* @__PURE__ */ new Set([
|
|
242
|
-
"store",
|
|
243
|
-
"autoCleanup",
|
|
244
|
-
"autoCleanupMs",
|
|
245
|
-
"clientKey",
|
|
246
|
-
"clientMaxAge",
|
|
247
|
-
"clientAlwaysRoll"
|
|
248
|
-
]);
|
|
249
|
-
|
|
250
|
-
// src/class/SessionStore.js
|
|
251
|
-
var formatState = (session2, maxAge, prevTTL, maxAgeDefault) => {
|
|
252
|
-
const ttl = maxAge ?? prevTTL ?? maxAgeDefault;
|
|
253
|
-
const expiresAt = Date.now() + ttl;
|
|
254
|
-
return { session: session2, expiresAt, ttl };
|
|
255
|
-
};
|
|
256
|
-
var SessionStore = class extends Map {
|
|
257
|
-
constructor(opt = {}) {
|
|
258
|
-
super();
|
|
259
|
-
const maxAge = validRange(ms.s(), ms.y(), opt.maxAge, false, "maxAge") ?? ms.M();
|
|
260
|
-
const autoCleanup = valid("boolean", opt.autoCleanup, false, "autoCleanup") ?? true;
|
|
261
|
-
const autoCleanupMs = validInterval(opt.autoCleanupMs, false, "autoCleanupMs") ?? Math.max(ms.s(), Math.min(ms.h(), maxAge / 10));
|
|
262
|
-
(0, import_props2.solid)(this, "maxAge", maxAge);
|
|
263
|
-
(0, import_props2.solid)(this, "event", new import_events.EventEmitter());
|
|
264
|
-
if (!autoCleanup) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
setInterval((_) => this.cleanup(), autoCleanupMs);
|
|
268
|
-
}
|
|
269
|
-
on(eventName, callback) {
|
|
270
|
-
return this.event.on(eventName, callback);
|
|
271
|
-
}
|
|
272
|
-
get(sid) {
|
|
273
|
-
const d = super.get(sid);
|
|
274
|
-
if (!d) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (Date.now() < d.expiresAt) {
|
|
278
|
-
return d.session;
|
|
200
|
+
var createLock = (sid) => {
|
|
201
|
+
let _release;
|
|
202
|
+
const previous = sidLocks.get(sid);
|
|
203
|
+
const current = new Promise((resolve) => {
|
|
204
|
+
_release = resolve;
|
|
205
|
+
});
|
|
206
|
+
sidLocks.set(sid, current);
|
|
207
|
+
const release = () => {
|
|
208
|
+
_release();
|
|
209
|
+
if (sidLocks.get(sid) === current) {
|
|
210
|
+
sidLocks.delete(sid);
|
|
279
211
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
212
|
+
};
|
|
213
|
+
return [previous, release];
|
|
214
|
+
};
|
|
215
|
+
var applySessionHandler = async (socket, handler, gw, onMissing) => {
|
|
216
|
+
valid("function", handler, true, "handler");
|
|
217
|
+
for (let i = 0; i < 5; i++) {
|
|
218
|
+
const sid = socket.sessionId;
|
|
219
|
+
if (!sid) {
|
|
220
|
+
return applyOnMissing(onMissing);
|
|
286
221
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
delete(sid) {
|
|
292
|
-
return this.destroy(sid);
|
|
293
|
-
}
|
|
294
|
-
destroy(sid) {
|
|
295
|
-
if (this.has(sid)) {
|
|
296
|
-
super.delete(sid);
|
|
297
|
-
this.event.emit("destroy", this, sid);
|
|
222
|
+
const [previous, release] = createLock(sid);
|
|
223
|
+
if (previous) {
|
|
224
|
+
await previous;
|
|
298
225
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const now = Date.now();
|
|
303
|
-
let cleared = 0;
|
|
304
|
-
for (const [sid, d] of this.entries()) {
|
|
305
|
-
if (now < d.expiresAt) {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
if (this.destroy(sid)) {
|
|
309
|
-
cleared++;
|
|
310
|
-
}
|
|
226
|
+
if (previous && sid !== socket.sessionId) {
|
|
227
|
+
release();
|
|
228
|
+
continue;
|
|
311
229
|
}
|
|
312
|
-
|
|
313
|
-
|
|
230
|
+
try {
|
|
231
|
+
return await runSessionHandler(sid, socket, handler, gw, onMissing);
|
|
232
|
+
} finally {
|
|
233
|
+
release();
|
|
314
234
|
}
|
|
315
|
-
return cleared;
|
|
316
235
|
}
|
|
236
|
+
throw new Error(`${_errPrefix} socket.sessionId changed during withSession execution.`);
|
|
317
237
|
};
|
|
318
238
|
|
|
319
239
|
// src/formatOptions.js
|
|
@@ -326,9 +246,7 @@ var pickKoaOpt = (rawOpt) => {
|
|
|
326
246
|
koaOpt[key] = rawOpt[key];
|
|
327
247
|
}
|
|
328
248
|
koaOpt.key = valid("string", koaOpt.key, false, "key") ?? generateUid(12);
|
|
329
|
-
koaOpt.maxAge = validRange(ms.s(), ms.y(), koaOpt.maxAge, false, "maxAge") ?? ms.M();
|
|
330
249
|
koaOpt.signed = valid("boolean", koaOpt.signed, false, "signed") ?? true;
|
|
331
|
-
koaOpt.store = validStore(rawOpt.store || new SessionStore(rawOpt));
|
|
332
250
|
return koaOpt;
|
|
333
251
|
};
|
|
334
252
|
var formatOptions = (opt = {}) => {
|
|
@@ -346,11 +264,11 @@ var formatOptions = (opt = {}) => {
|
|
|
346
264
|
};
|
|
347
265
|
|
|
348
266
|
// src/class/Bridge.js
|
|
349
|
-
var
|
|
267
|
+
var import_props2 = require("@randajan/props");
|
|
350
268
|
var Bridge = class {
|
|
351
269
|
constructor(opt = {}) {
|
|
352
270
|
const { onSet, onDelete } = opt;
|
|
353
|
-
(0,
|
|
271
|
+
(0, import_props2.solids)(this, {
|
|
354
272
|
onSet,
|
|
355
273
|
onDelete,
|
|
356
274
|
s2c: /* @__PURE__ */ new Map(),
|
|
@@ -368,7 +286,7 @@ var Bridge = class {
|
|
|
368
286
|
}
|
|
369
287
|
this.c2s.set(cid, sid);
|
|
370
288
|
this.s2c.set(sid, cid);
|
|
371
|
-
this.onSet(
|
|
289
|
+
this.onSet(cid, sid);
|
|
372
290
|
return true;
|
|
373
291
|
}
|
|
374
292
|
getByCid(cid) {
|
|
@@ -387,7 +305,7 @@ var Bridge = class {
|
|
|
387
305
|
}
|
|
388
306
|
this.s2c.delete(sid);
|
|
389
307
|
this.c2s.delete(cid);
|
|
390
|
-
this.onDelete(
|
|
308
|
+
this.onDelete(cid, sid);
|
|
391
309
|
return true;
|
|
392
310
|
}
|
|
393
311
|
deleteByCid(cid, skipIf) {
|
|
@@ -400,30 +318,245 @@ var Bridge = class {
|
|
|
400
318
|
}
|
|
401
319
|
this.c2s.delete(cid);
|
|
402
320
|
this.s2c.delete(sid);
|
|
403
|
-
this.onDelete(
|
|
321
|
+
this.onDelete(cid, sid);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// src/class/TempMap.js
|
|
327
|
+
var TempMap = class extends Map {
|
|
328
|
+
constructor(ttl) {
|
|
329
|
+
super();
|
|
330
|
+
const _p = {
|
|
331
|
+
ttl: validInterval(ttl, true, "ttl"),
|
|
332
|
+
ts: /* @__PURE__ */ new Map()
|
|
333
|
+
};
|
|
334
|
+
_privs.set(this, _p);
|
|
335
|
+
}
|
|
336
|
+
set(key, value, overwrite = true) {
|
|
337
|
+
const { ts, ttl } = _privs.get(this);
|
|
338
|
+
if (!overwrite && this.has(key)) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
this.delete(key);
|
|
342
|
+
super.set(key, value);
|
|
343
|
+
const t = setTimeout((_) => {
|
|
344
|
+
this.delete(key);
|
|
345
|
+
}, ttl);
|
|
346
|
+
ts.set(key, t);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
get(key, andDelete = false) {
|
|
350
|
+
const v = super.get(key);
|
|
351
|
+
if (andDelete) {
|
|
352
|
+
this.delete(key);
|
|
353
|
+
}
|
|
354
|
+
return v;
|
|
355
|
+
}
|
|
356
|
+
delete(key) {
|
|
357
|
+
if (!super.delete(key)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const { ts } = _privs.get(this);
|
|
361
|
+
const t = ts.get(key);
|
|
362
|
+
clearTimeout(t);
|
|
363
|
+
ts.delete(key);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// src/class/StoreGateway.js
|
|
369
|
+
var import_props3 = require("@randajan/props");
|
|
370
|
+
|
|
371
|
+
// src/stores/LiveStore.js
|
|
372
|
+
var LiveStore = class extends Map {
|
|
373
|
+
get(sid) {
|
|
374
|
+
return super.get(sid);
|
|
375
|
+
}
|
|
376
|
+
set(sid, state) {
|
|
377
|
+
super.set(sid, state);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
destroy(sid) {
|
|
381
|
+
return super.delete(sid);
|
|
382
|
+
}
|
|
383
|
+
list() {
|
|
384
|
+
return this.keys();
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/class/StoreGateway.js
|
|
389
|
+
var formatState = (session2, maxAge, maxAgeDefault) => {
|
|
390
|
+
const ttl = maxAge ?? maxAgeDefault;
|
|
391
|
+
const expiresAt = Date.now() + ttl;
|
|
392
|
+
return { session: session2, expiresAt, ttl };
|
|
393
|
+
};
|
|
394
|
+
var requireList = (store) => {
|
|
395
|
+
if (!store.list) {
|
|
396
|
+
throw new TypeError(`${_errPrefix} store.list() is required`);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var StoreGateway = class _StoreGateway {
|
|
400
|
+
static is(any) {
|
|
401
|
+
return any instanceof _StoreGateway;
|
|
402
|
+
}
|
|
403
|
+
constructor(opt = {}, emit = {}) {
|
|
404
|
+
const maxAge = validRange(opt.maxAge, ms.m(), ms.y(), false, "maxAge") ?? ms.M();
|
|
405
|
+
const autoCleanup = valid("boolean", opt.autoCleanup, false, "autoCleanup") ?? false;
|
|
406
|
+
(0, import_props3.solid)(this, "emit", emit);
|
|
407
|
+
(0, import_props3.solid)(this, "maxAge", maxAge);
|
|
408
|
+
(0, import_props3.solid)(this, "store", validStore(opt.store, false) ?? new LiveStore());
|
|
409
|
+
if (autoCleanup) {
|
|
410
|
+
this.startAutoCleanup(validInterval(opt.autoCleanupMs, false, "autoCleanupMs"));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async list() {
|
|
414
|
+
const { store } = this;
|
|
415
|
+
requireList(store);
|
|
416
|
+
return store.list();
|
|
417
|
+
}
|
|
418
|
+
async get(sid) {
|
|
419
|
+
const { store } = this;
|
|
420
|
+
const c = await store.get(sid);
|
|
421
|
+
if (!c) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (Date.now() >= c.expiresAt) {
|
|
425
|
+
await this.destroy(sid);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
return JSON.parse(c.session);
|
|
430
|
+
} catch {
|
|
431
|
+
await this.destroy(sid);
|
|
432
|
+
throw new Error(`${_errPrefix} Invalid session JSON for sid='${sid}'.`);
|
|
433
|
+
}
|
|
434
|
+
;
|
|
435
|
+
}
|
|
436
|
+
async set(sid, session2, maxAge) {
|
|
437
|
+
const { store } = this;
|
|
438
|
+
const ses = validObject(session2, false, "session");
|
|
439
|
+
if (ses == null) {
|
|
440
|
+
return this.destroy(sid);
|
|
441
|
+
}
|
|
442
|
+
const to = JSON.stringify(ses);
|
|
443
|
+
const from = await store.get(sid, false);
|
|
444
|
+
if (to === from?.session) {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
const isOk = await store.set(sid, formatState(to, maxAge, this.maxAge));
|
|
448
|
+
if (isOk === false) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
this.emit.notifySet(sid, !from);
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
async destroy(sid) {
|
|
455
|
+
const { store } = this;
|
|
456
|
+
const isOk = await store.destroy(sid);
|
|
457
|
+
if (isOk === false) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
this.emit.notifyDestroy(sid);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
async cleanup() {
|
|
464
|
+
const { store } = this;
|
|
465
|
+
requireList(store);
|
|
466
|
+
const list = await store.list();
|
|
467
|
+
const now = Date.now();
|
|
468
|
+
let cleared = 0;
|
|
469
|
+
await Promise.all([...list].map(async (sid) => {
|
|
470
|
+
const d = await store.get(sid);
|
|
471
|
+
if (!d) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (now < d.expiresAt) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (await this.destroy(sid)) {
|
|
478
|
+
cleared++;
|
|
479
|
+
}
|
|
480
|
+
}));
|
|
481
|
+
if (is("function", store.optimize)) {
|
|
482
|
+
await store.optimize(cleared);
|
|
483
|
+
}
|
|
484
|
+
this.emit.notifyCleanup(cleared);
|
|
485
|
+
return cleared;
|
|
486
|
+
}
|
|
487
|
+
startAutoCleanup(interval) {
|
|
488
|
+
const { store, maxAge, _intId } = this;
|
|
489
|
+
requireList(store);
|
|
490
|
+
const int = validRange(interval, ms.m(), ms.d(), false, "interval") ?? Math.max(ms.m(), Math.min(ms.d(), maxAge / 4));
|
|
491
|
+
clearInterval(_intId);
|
|
492
|
+
this._intId = setInterval((_) => this.cleanup().catch(() => {
|
|
493
|
+
}), int);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
stopAutoCleanup() {
|
|
497
|
+
const { _intId } = this;
|
|
498
|
+
if (!_intId) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
clearInterval(_intId);
|
|
502
|
+
delete this._intId;
|
|
404
503
|
return true;
|
|
405
504
|
}
|
|
406
505
|
};
|
|
407
506
|
|
|
408
507
|
// src/class/SessionBridge.js
|
|
409
|
-
var SessionBridge = class extends
|
|
508
|
+
var SessionBridge = class extends import_events.EventEmitter {
|
|
410
509
|
constructor(app, io, opt = {}) {
|
|
411
510
|
super();
|
|
412
511
|
if (!app.keys) {
|
|
413
512
|
app.keys = Array(6).fill().map(() => generateUid(12));
|
|
414
513
|
}
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
514
|
+
const { koaOpt, clientOpt, clientAlwaysRoll } = formatOptions(opt);
|
|
515
|
+
const _p = {};
|
|
516
|
+
const tmp = new TempMap(5e3);
|
|
517
|
+
const brg = _p.brg = new Bridge({
|
|
518
|
+
onSet: (clientId, sessionId) => {
|
|
519
|
+
const isNew = !!tmp.get(sessionId, true);
|
|
520
|
+
this.emit("sessionSet", { clientId, sessionId, isNew, isInit: true });
|
|
521
|
+
},
|
|
522
|
+
onDelete: (clientId, sessionId) => this.emit("sessionDestroy", { clientId, sessionId })
|
|
420
523
|
});
|
|
421
|
-
|
|
422
|
-
|
|
524
|
+
_p.notifySet = (sid, isNew) => {
|
|
525
|
+
if (!sid) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const cid = brg.getBySid(sid);
|
|
529
|
+
if (!cid) {
|
|
530
|
+
tmp.set(sid, isNew, false);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
this.emit("sessionSet", { clientId: cid, sessionId: sid, isNew: !!isNew, isInit: false });
|
|
534
|
+
};
|
|
535
|
+
_p.notifyDestroy = (sid) => {
|
|
536
|
+
if (sid) {
|
|
537
|
+
brg.deleteBySid(sid);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
_p.notifyCleanup = (cleared) => {
|
|
541
|
+
this.emit("cleanup", cleared);
|
|
542
|
+
};
|
|
543
|
+
const gw = _p.gw = new StoreGateway(opt, _p);
|
|
544
|
+
const cc = createClientCookie(clientOpt);
|
|
545
|
+
const [koaSession, sc] = createKoaSession(app, gw, koaOpt, (ctx, sid) => {
|
|
423
546
|
const cid = cc.get(ctx);
|
|
424
547
|
brg.set(cid, sid);
|
|
425
548
|
});
|
|
426
|
-
const
|
|
549
|
+
const reviveCid = (ctx) => {
|
|
550
|
+
let cid = cc.get(ctx);
|
|
551
|
+
if (!cid) {
|
|
552
|
+
cc.set(ctx, cid = generateUid(24));
|
|
553
|
+
} else if (clientAlwaysRoll) {
|
|
554
|
+
cc.set(ctx, cid);
|
|
555
|
+
}
|
|
556
|
+
return cid;
|
|
557
|
+
};
|
|
558
|
+
const reviveSid = async (ctx, cid) => {
|
|
559
|
+
const reqSid = sc.get(ctx);
|
|
427
560
|
if (cid == null || reqSid == null) {
|
|
428
561
|
return;
|
|
429
562
|
}
|
|
@@ -437,21 +570,15 @@ var SessionBridge = class extends import_events2.EventEmitter {
|
|
|
437
570
|
if (brg.getBySid(reqSid)) {
|
|
438
571
|
return;
|
|
439
572
|
}
|
|
440
|
-
if (!await
|
|
573
|
+
if (!await gw.get(reqSid)) {
|
|
441
574
|
return;
|
|
442
575
|
}
|
|
443
576
|
brg.set(cid, reqSid);
|
|
444
577
|
};
|
|
445
578
|
app.use(koaSession);
|
|
446
579
|
app.use(async (ctx, next) => {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (!cid) {
|
|
450
|
-
cc.set(ctx, cid = generateUid(24));
|
|
451
|
-
} else if (o.clientAlwaysRoll) {
|
|
452
|
-
cc.set(ctx, cid);
|
|
453
|
-
}
|
|
454
|
-
await regenerateSid(ctx, cid, sid);
|
|
580
|
+
const cid = reviveCid(ctx);
|
|
581
|
+
await reviveSid(ctx, cid);
|
|
455
582
|
(0, import_props4.solid)(ctx, "clientId", cid);
|
|
456
583
|
(0, import_props4.virtual)(ctx, "sessionId", (_) => brg.getByCid(cid));
|
|
457
584
|
await next();
|
|
@@ -462,24 +589,87 @@ var SessionBridge = class extends import_events2.EventEmitter {
|
|
|
462
589
|
const ctx = app.createContext(req, res);
|
|
463
590
|
await koaSession(ctx, async () => {
|
|
464
591
|
});
|
|
465
|
-
const cid =
|
|
466
|
-
|
|
467
|
-
await regenerateSid(ctx, cid, sid);
|
|
592
|
+
const cid = reviveCid(ctx);
|
|
593
|
+
await reviveSid(ctx, cid);
|
|
468
594
|
(0, import_props4.solid)(socket, "clientId", cid);
|
|
469
595
|
(0, import_props4.virtual)(socket, "sessionId", (_) => brg.getByCid(cid));
|
|
470
596
|
(0, import_props4.solid)(socket, "withSession", async function(handler, onMissing) {
|
|
471
|
-
const onm = arguments.length > 1 ? onMissing : new Error(
|
|
472
|
-
return applySessionHandler(socket, handler,
|
|
597
|
+
const onm = arguments.length > 1 ? onMissing : new Error(`${_errPrefix} Session is missing for this socket.`);
|
|
598
|
+
return applySessionHandler(socket, handler, gw, onm);
|
|
473
599
|
}, false);
|
|
474
600
|
await next();
|
|
475
601
|
});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
602
|
+
_privs.set(this, _p);
|
|
603
|
+
}
|
|
604
|
+
async getById(sessionId) {
|
|
605
|
+
const { gw, brg } = _privs.get(this);
|
|
606
|
+
const cid = brg.getBySid(sessionId);
|
|
607
|
+
if (cid) {
|
|
608
|
+
return gw.get(sessionId);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async destroyById(sessionId) {
|
|
612
|
+
const { gw, brg } = _privs.get(this);
|
|
613
|
+
const cid = brg.getBySid(sessionId);
|
|
614
|
+
if (cid) {
|
|
615
|
+
return gw.destroy(sessionId);
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
async setById(sessionId, session2, maxAge) {
|
|
620
|
+
const { gw, brg } = _privs.get(this);
|
|
621
|
+
const cid = brg.getBySid(sessionId);
|
|
622
|
+
if (cid) {
|
|
623
|
+
return gw.set(sessionId, session2, maxAge);
|
|
624
|
+
}
|
|
625
|
+
throw new Error(`${_errPrefix} Creating session via setById() is prohibited. sessionId='${sessionId}'.`);
|
|
626
|
+
}
|
|
627
|
+
async getByClientId(clientId) {
|
|
628
|
+
const { gw, brg } = _privs.get(this);
|
|
629
|
+
const sid = brg.getByCid(clientId);
|
|
630
|
+
if (sid) {
|
|
631
|
+
return gw.get(sid);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
async destroyByClientId(clientId) {
|
|
635
|
+
const { gw, brg } = _privs.get(this);
|
|
636
|
+
const sid = brg.getByCid(clientId);
|
|
637
|
+
if (sid) {
|
|
638
|
+
return gw.destroy(sid);
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
async setByClientId(clientId, session2, maxAge) {
|
|
643
|
+
const { gw, brg } = _privs.get(this);
|
|
644
|
+
const sid = brg.getByCid(clientId);
|
|
645
|
+
if (sid) {
|
|
646
|
+
return gw.set(sid, session2, maxAge);
|
|
647
|
+
}
|
|
648
|
+
throw new Error(`${_errPrefix} Creating session via setByClientId() is prohibited. clientId='${clientId}'.`);
|
|
649
|
+
}
|
|
650
|
+
getSessionId(clientId) {
|
|
651
|
+
return _privs.get(this).brg.getByCid(clientId);
|
|
652
|
+
}
|
|
653
|
+
getClientId(sessionId) {
|
|
654
|
+
return _privs.get(this).brg.getBySid(sessionId);
|
|
655
|
+
}
|
|
656
|
+
async cleanup() {
|
|
657
|
+
return _privs.get(this).gw.cleanup();
|
|
658
|
+
}
|
|
659
|
+
startAutoCleanup(interval) {
|
|
660
|
+
return _privs.get(this).gw.startAutoCleanup(interval);
|
|
661
|
+
}
|
|
662
|
+
stopAutoCleanup() {
|
|
663
|
+
return _privs.get(this).gw.stopAutoCleanup();
|
|
664
|
+
}
|
|
665
|
+
notifyStoreSet(sessionId, isNew) {
|
|
666
|
+
return _privs.get(this).notifySet(sessionId, isNew);
|
|
667
|
+
}
|
|
668
|
+
notifyStoreDestroy(sessionId) {
|
|
669
|
+
return _privs.get(this).notifyDestroy(sessionId);
|
|
670
|
+
}
|
|
671
|
+
notifyStoreCleanup(clearedCount) {
|
|
672
|
+
return _privs.get(this).notifyCleanup(clearedCount);
|
|
483
673
|
}
|
|
484
674
|
};
|
|
485
675
|
|