@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.
@@ -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,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, store, onMissing) => {
198
- const sid = socket.sessionId;
199
- const current = await store.get(sid);
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 session2 = current;
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 (sessionCtx.session == null) {
208
- await store.destroy(sid);
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 applySessionHandler = async (socket, handler, store, onMissing) => {
218
- if (typeof handler !== "function") {
219
- throw new TypeError("socket.withSession(handler) requires a function");
220
- }
221
- if (!socket.sessionId) {
222
- return applyOnMissing(onMissing);
223
- }
224
- return withLock(runSessionHandler, socket, handler, store, onMissing);
225
- };
226
-
227
- // src/class/SessionStore.js
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
- this.delete(sid);
281
- }
282
- set(sid, session2, maxAge) {
283
- const d = super.get(sid);
284
- if (session2 == null) {
285
- return !d || this.destroy(sid);
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
- super.set(sid, formatState(session2, maxAge, d?.ttl, this.maxAge));
288
- this.event.emit("set", this, sid, !d);
289
- return true;
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
- return true;
300
- }
301
- cleanup() {
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
- if (cleared) {
313
- this.event.emit("cleanup", this, cleared);
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 import_props3 = require("@randajan/props");
267
+ var import_props2 = require("@randajan/props");
350
268
  var Bridge = class {
351
269
  constructor(opt = {}) {
352
270
  const { onSet, onDelete } = opt;
353
- (0, import_props3.solids)(this, {
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({ clientId: cid, sessionId: sid });
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({ clientId: cid, sessionId: sid });
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({ 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;
404
503
  return true;
405
504
  }
406
505
  };
407
506
 
408
507
  // src/class/SessionBridge.js
409
- var SessionBridge = class extends import_events2.EventEmitter {
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 o = formatOptions(opt);
416
- const { store } = o.koaOpt;
417
- const brg = new Bridge({
418
- onSet: (pair) => this.emit("sessionStart", pair),
419
- 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 })
420
523
  });
421
- const cc = createClientCookie(o.clientOpt);
422
- 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) => {
423
546
  const cid = cc.get(ctx);
424
547
  brg.set(cid, sid);
425
548
  });
426
- 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);
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 store.get(reqSid)) {
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
- let cid = cc.get(ctx);
448
- const sid = sc.get(ctx);
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 = cc.get(ctx);
466
- const sid = sc.get(ctx);
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("Session missing");
472
- return applySessionHandler(socket, handler, store, onm);
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
- store.on("destroy", (_store, sid) => {
477
- if (!sid) {
478
- return;
479
- }
480
- brg.deleteBySid(sid);
481
- });
482
- (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);
483
673
  }
484
674
  };
485
675