@randajan/koa-io-session 2.2.0 → 3.0.1

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