@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.
@@ -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 import_events2 = require("events");
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 Error(`${msg} require typeof '${type}'`);
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 Error(`${msg} is not typeof '${type}'`);
85
+ throw new TypeError(_err(`Invalid '${msg}'. Expected type '${type}', received '${_typeOf(any)}'.`));
60
86
  };
61
- var validRange = (min, max, any, req = false, msg = "argument") => {
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 Error(`${msg} must be greater than ${min}`);
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, any, req, msg);
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 Error(`${msg} must be object, not array`);
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?.get)) {
116
+ if (!is("function", store.get)) {
90
117
  missing.push("get()");
91
118
  }
92
- if (!is("function", store?.set)) {
119
+ if (!is("function", store.set)) {
93
120
  missing.push("set()");
94
121
  }
95
- if (!is("function", store?.destroy)) {
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 is missing required API: ${missing.join(", ")}`);
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 = (opt, app, onSet) => {
131
- const store = wrapStore(opt.store);
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 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;
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 isSessionHashChanged = (originalHash, session2) => {
162
- const nextHash = createSessionHash(session2);
163
- if (originalHash == null || nextHash == null) {
164
- return true;
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 originalHash !== nextHash;
198
+ return result;
167
199
  };
168
- var withLock = async (task, socket, ...args) => {
169
- const sid = socket.sessionId;
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
- releaseCurrent = resolve;
204
+ _release = resolve;
174
205
  });
175
206
  sidLocks.set(sid, current);
176
- if (previous) {
177
- await previous;
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 SessionStore = class extends Map {
248
- constructor(opt = {}) {
249
- super();
250
- const maxAge = validRange(ms.s(), ms.y(), opt.maxAge, false, "maxAge") ?? ms.M();
251
- const autoCleanup = valid("boolean", opt.autoCleanup, false, "autoCleanup") ?? true;
252
- const autoCleanupMs = validInterval(opt.autoCleanupMs, false, "autoCleanupMs") ?? Math.max(ms.s(), Math.min(ms.h(), maxAge / 10));
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
- this.delete(sid);
272
- }
273
- set(sid, session2, maxAge) {
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
- super.set(sid, formatState(session2, maxAge, d?.ttl, this.maxAge));
279
- this.event.emit("set", this, sid, !d);
280
- return true;
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
- if (cleared) {
304
- this.event.emit("cleanup", this, cleared);
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 import_props3 = require("@randajan/props");
267
+ var import_props2 = require("@randajan/props");
341
268
  var Bridge = class {
342
269
  constructor(opt = {}) {
343
270
  const { onSet, onDelete } = opt;
344
- (0, import_props3.solids)(this, {
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({ clientId: cid, sessionId: sid });
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({ clientId: cid, sessionId: sid });
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({ clientId: cid, sessionId: sid });
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 import_events2.EventEmitter {
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 o = formatOptions(opt);
407
- const { store } = o.koaOpt;
408
- const brg = new Bridge({
409
- onSet: (pair) => this.emit("sessionStart", pair),
410
- onDelete: (pair) => this.emit("sessionEnd", pair)
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
- const cc = createClientCookie(o.clientOpt);
413
- const [koaSession, sc] = createKoaSession(o.koaOpt, app, (ctx, sid) => {
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 regenerateSid = async (ctx, cid, reqSid) => {
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 store.get(reqSid)) {
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
- let cid = cc.get(ctx);
439
- const sid = sc.get(ctx);
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 = cc.get(ctx);
457
- const sid = sc.get(ctx);
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
- return applySessionHandler(socket, handler, store);
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
- store.on("destroy", (_store, sid) => {
467
- if (!sid) {
468
- return;
469
- }
470
- brg.deleteBySid(sid);
471
- });
472
- (0, import_props4.solid)(this, "store", store);
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