@randajan/koa-io-session 2.1.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 +162 -96
- package/dist/cjs/index.cjs +399 -199
- 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 +369 -241
- 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,161 +176,64 @@ 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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
179
|
+
var applyOnMissing = (onMissing) => {
|
|
180
|
+
if (onMissing instanceof Error) {
|
|
181
|
+
throw onMissing;
|
|
182
|
+
}
|
|
183
|
+
if (is("function", onMissing)) {
|
|
184
|
+
return onMissing();
|
|
159
185
|
}
|
|
186
|
+
return onMissing;
|
|
160
187
|
};
|
|
161
|
-
var
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
return
|
|
188
|
+
var runSessionHandler = async (sid, socket, handler, gw, onMissing) => {
|
|
189
|
+
const session2 = await gw.get(sid);
|
|
190
|
+
if (!session2) {
|
|
191
|
+
return applyOnMissing(onMissing);
|
|
192
|
+
}
|
|
193
|
+
const sessionCtx = (0, import_props.solids)({ session: session2 }, { sessionId: sid, socket });
|
|
194
|
+
const result = await handler(sessionCtx, socket);
|
|
195
|
+
if (sid === socket.sessionId) {
|
|
196
|
+
await gw.set(sid, sessionCtx.session);
|
|
165
197
|
}
|
|
166
|
-
return
|
|
198
|
+
return result;
|
|
167
199
|
};
|
|
168
|
-
var
|
|
169
|
-
|
|
200
|
+
var createLock = (sid) => {
|
|
201
|
+
let _release;
|
|
170
202
|
const previous = sidLocks.get(sid);
|
|
171
|
-
let releaseCurrent;
|
|
172
203
|
const current = new Promise((resolve) => {
|
|
173
|
-
|
|
204
|
+
_release = resolve;
|
|
174
205
|
});
|
|
175
206
|
sidLocks.set(sid, current);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
return await task(socket, ...args);
|
|
181
|
-
} finally {
|
|
182
|
-
releaseCurrent();
|
|
207
|
+
const release = () => {
|
|
208
|
+
_release();
|
|
183
209
|
if (sidLocks.get(sid) === current) {
|
|
184
210
|
sidLocks.delete(sid);
|
|
185
211
|
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
var runSessionHandler = async (socket, handler, store) => {
|
|
189
|
-
const sid = socket.sessionId;
|
|
190
|
-
const current = await store.get(sid);
|
|
191
|
-
if (!current) {
|
|
192
|
-
throw new Error("Session not found");
|
|
193
|
-
}
|
|
194
|
-
const session2 = current;
|
|
195
|
-
const sessionCtx = createSessionCtx(sid, session2, socket);
|
|
196
|
-
const originalHash = createSessionHash(sessionCtx.session);
|
|
197
|
-
const result = await handler(sessionCtx, socket);
|
|
198
|
-
if (sessionCtx.session == null) {
|
|
199
|
-
await store.destroy(sid);
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
sessionCtx.session = validObject(sessionCtx.session, false, "session");
|
|
203
|
-
if (isSessionHashChanged(originalHash, sessionCtx.session)) {
|
|
204
|
-
await store.set(sid, sessionCtx.session);
|
|
205
|
-
}
|
|
206
|
-
return result;
|
|
207
|
-
};
|
|
208
|
-
var applySessionHandler = async (socket, handler, store) => {
|
|
209
|
-
if (typeof handler !== "function") {
|
|
210
|
-
throw new TypeError("socket.withSession(handler) requires a function");
|
|
211
|
-
}
|
|
212
|
-
if (!socket.sessionId) {
|
|
213
|
-
throw new Error("Missing session id");
|
|
214
|
-
}
|
|
215
|
-
return withLock(runSessionHandler, socket, handler, store);
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// src/class/SessionStore.js
|
|
219
|
-
var import_props2 = require("@randajan/props");
|
|
220
|
-
var import_events = require("events");
|
|
221
|
-
|
|
222
|
-
// src/const.js
|
|
223
|
-
var ms = {
|
|
224
|
-
s: (v = 1) => v * 1e3,
|
|
225
|
-
m: (v = 1) => ms.s(v * 60),
|
|
226
|
-
h: (v = 1) => ms.m(v * 60),
|
|
227
|
-
d: (v = 1) => ms.h(v * 24),
|
|
228
|
-
w: (v = 1) => ms.d(v * 7),
|
|
229
|
-
M: (v = 1) => ms.d(v * 30),
|
|
230
|
-
y: (v = 1) => ms.d(v * 365)
|
|
231
|
-
};
|
|
232
|
-
var _customOptKeys = /* @__PURE__ */ new Set([
|
|
233
|
-
"store",
|
|
234
|
-
"autoCleanup",
|
|
235
|
-
"autoCleanupMs",
|
|
236
|
-
"clientKey",
|
|
237
|
-
"clientMaxAge",
|
|
238
|
-
"clientAlwaysRoll"
|
|
239
|
-
]);
|
|
240
|
-
|
|
241
|
-
// src/class/SessionStore.js
|
|
242
|
-
var formatState = (session2, maxAge, prevTTL, maxAgeDefault) => {
|
|
243
|
-
const ttl = maxAge ?? prevTTL ?? maxAgeDefault;
|
|
244
|
-
const expiresAt = Date.now() + ttl;
|
|
245
|
-
return { session: session2, expiresAt, ttl };
|
|
212
|
+
};
|
|
213
|
+
return [previous, release];
|
|
246
214
|
};
|
|
247
|
-
var
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
(0, import_props2.solid)(this, "maxAge", maxAge);
|
|
254
|
-
(0, import_props2.solid)(this, "event", new import_events.EventEmitter());
|
|
255
|
-
if (!autoCleanup) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
setInterval((_) => this.cleanup(), autoCleanupMs);
|
|
259
|
-
}
|
|
260
|
-
on(eventName, callback) {
|
|
261
|
-
return this.event.on(eventName, callback);
|
|
262
|
-
}
|
|
263
|
-
get(sid) {
|
|
264
|
-
const d = super.get(sid);
|
|
265
|
-
if (!d) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (Date.now() < d.expiresAt) {
|
|
269
|
-
return d.session;
|
|
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);
|
|
270
221
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const d = super.get(sid);
|
|
275
|
-
if (session2 == null) {
|
|
276
|
-
return !d || this.destroy(sid);
|
|
222
|
+
const [previous, release] = createLock(sid);
|
|
223
|
+
if (previous) {
|
|
224
|
+
await previous;
|
|
277
225
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
delete(sid) {
|
|
283
|
-
return this.destroy(sid);
|
|
284
|
-
}
|
|
285
|
-
destroy(sid) {
|
|
286
|
-
if (this.has(sid)) {
|
|
287
|
-
super.delete(sid);
|
|
288
|
-
this.event.emit("destroy", this, sid);
|
|
289
|
-
}
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
cleanup() {
|
|
293
|
-
const now = Date.now();
|
|
294
|
-
let cleared = 0;
|
|
295
|
-
for (const [sid, d] of this.entries()) {
|
|
296
|
-
if (now < d.expiresAt) {
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (this.destroy(sid)) {
|
|
300
|
-
cleared++;
|
|
301
|
-
}
|
|
226
|
+
if (previous && sid !== socket.sessionId) {
|
|
227
|
+
release();
|
|
228
|
+
continue;
|
|
302
229
|
}
|
|
303
|
-
|
|
304
|
-
|
|
230
|
+
try {
|
|
231
|
+
return await runSessionHandler(sid, socket, handler, gw, onMissing);
|
|
232
|
+
} finally {
|
|
233
|
+
release();
|
|
305
234
|
}
|
|
306
|
-
return cleared;
|
|
307
235
|
}
|
|
236
|
+
throw new Error(`${_errPrefix} socket.sessionId changed during withSession execution.`);
|
|
308
237
|
};
|
|
309
238
|
|
|
310
239
|
// src/formatOptions.js
|
|
@@ -317,9 +246,7 @@ var pickKoaOpt = (rawOpt) => {
|
|
|
317
246
|
koaOpt[key] = rawOpt[key];
|
|
318
247
|
}
|
|
319
248
|
koaOpt.key = valid("string", koaOpt.key, false, "key") ?? generateUid(12);
|
|
320
|
-
koaOpt.maxAge = validRange(ms.s(), ms.y(), koaOpt.maxAge, false, "maxAge") ?? ms.M();
|
|
321
249
|
koaOpt.signed = valid("boolean", koaOpt.signed, false, "signed") ?? true;
|
|
322
|
-
koaOpt.store = validStore(rawOpt.store || new SessionStore(rawOpt));
|
|
323
250
|
return koaOpt;
|
|
324
251
|
};
|
|
325
252
|
var formatOptions = (opt = {}) => {
|
|
@@ -337,11 +264,11 @@ var formatOptions = (opt = {}) => {
|
|
|
337
264
|
};
|
|
338
265
|
|
|
339
266
|
// src/class/Bridge.js
|
|
340
|
-
var
|
|
267
|
+
var import_props2 = require("@randajan/props");
|
|
341
268
|
var Bridge = class {
|
|
342
269
|
constructor(opt = {}) {
|
|
343
270
|
const { onSet, onDelete } = opt;
|
|
344
|
-
(0,
|
|
271
|
+
(0, import_props2.solids)(this, {
|
|
345
272
|
onSet,
|
|
346
273
|
onDelete,
|
|
347
274
|
s2c: /* @__PURE__ */ new Map(),
|
|
@@ -359,7 +286,7 @@ var Bridge = class {
|
|
|
359
286
|
}
|
|
360
287
|
this.c2s.set(cid, sid);
|
|
361
288
|
this.s2c.set(sid, cid);
|
|
362
|
-
this.onSet(
|
|
289
|
+
this.onSet(cid, sid);
|
|
363
290
|
return true;
|
|
364
291
|
}
|
|
365
292
|
getByCid(cid) {
|
|
@@ -378,7 +305,7 @@ var Bridge = class {
|
|
|
378
305
|
}
|
|
379
306
|
this.s2c.delete(sid);
|
|
380
307
|
this.c2s.delete(cid);
|
|
381
|
-
this.onDelete(
|
|
308
|
+
this.onDelete(cid, sid);
|
|
382
309
|
return true;
|
|
383
310
|
}
|
|
384
311
|
deleteByCid(cid, skipIf) {
|
|
@@ -391,30 +318,245 @@ var Bridge = class {
|
|
|
391
318
|
}
|
|
392
319
|
this.c2s.delete(cid);
|
|
393
320
|
this.s2c.delete(sid);
|
|
394
|
-
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;
|
|
395
503
|
return true;
|
|
396
504
|
}
|
|
397
505
|
};
|
|
398
506
|
|
|
399
507
|
// src/class/SessionBridge.js
|
|
400
|
-
var SessionBridge = class extends
|
|
508
|
+
var SessionBridge = class extends import_events.EventEmitter {
|
|
401
509
|
constructor(app, io, opt = {}) {
|
|
402
510
|
super();
|
|
403
511
|
if (!app.keys) {
|
|
404
512
|
app.keys = Array(6).fill().map(() => generateUid(12));
|
|
405
513
|
}
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
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 })
|
|
411
523
|
});
|
|
412
|
-
|
|
413
|
-
|
|
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) => {
|
|
414
546
|
const cid = cc.get(ctx);
|
|
415
547
|
brg.set(cid, sid);
|
|
416
548
|
});
|
|
417
|
-
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);
|
|
418
560
|
if (cid == null || reqSid == null) {
|
|
419
561
|
return;
|
|
420
562
|
}
|
|
@@ -428,21 +570,15 @@ var SessionBridge = class extends import_events2.EventEmitter {
|
|
|
428
570
|
if (brg.getBySid(reqSid)) {
|
|
429
571
|
return;
|
|
430
572
|
}
|
|
431
|
-
if (!await
|
|
573
|
+
if (!await gw.get(reqSid)) {
|
|
432
574
|
return;
|
|
433
575
|
}
|
|
434
576
|
brg.set(cid, reqSid);
|
|
435
577
|
};
|
|
436
578
|
app.use(koaSession);
|
|
437
579
|
app.use(async (ctx, next) => {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (!cid) {
|
|
441
|
-
cc.set(ctx, cid = generateUid(24));
|
|
442
|
-
} else if (o.clientAlwaysRoll) {
|
|
443
|
-
cc.set(ctx, cid);
|
|
444
|
-
}
|
|
445
|
-
await regenerateSid(ctx, cid, sid);
|
|
580
|
+
const cid = reviveCid(ctx);
|
|
581
|
+
await reviveSid(ctx, cid);
|
|
446
582
|
(0, import_props4.solid)(ctx, "clientId", cid);
|
|
447
583
|
(0, import_props4.virtual)(ctx, "sessionId", (_) => brg.getByCid(cid));
|
|
448
584
|
await next();
|
|
@@ -453,23 +589,87 @@ var SessionBridge = class extends import_events2.EventEmitter {
|
|
|
453
589
|
const ctx = app.createContext(req, res);
|
|
454
590
|
await koaSession(ctx, async () => {
|
|
455
591
|
});
|
|
456
|
-
const cid =
|
|
457
|
-
|
|
458
|
-
await regenerateSid(ctx, cid, sid);
|
|
592
|
+
const cid = reviveCid(ctx);
|
|
593
|
+
await reviveSid(ctx, cid);
|
|
459
594
|
(0, import_props4.solid)(socket, "clientId", cid);
|
|
460
595
|
(0, import_props4.virtual)(socket, "sessionId", (_) => brg.getByCid(cid));
|
|
461
|
-
(0, import_props4.solid)(socket, "withSession", async (handler)
|
|
462
|
-
|
|
596
|
+
(0, import_props4.solid)(socket, "withSession", async function(handler, onMissing) {
|
|
597
|
+
const onm = arguments.length > 1 ? onMissing : new Error(`${_errPrefix} Session is missing for this socket.`);
|
|
598
|
+
return applySessionHandler(socket, handler, gw, onm);
|
|
463
599
|
}, false);
|
|
464
600
|
await next();
|
|
465
601
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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);
|
|
473
673
|
}
|
|
474
674
|
};
|
|
475
675
|
|