@randajan/koa-io-session 0.1.0 → 1.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 CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  [![NPM](https://img.shields.io/npm/v/@randajan/koa-io-session.svg)](https://www.npmjs.com/package/@randajan/koa-io-session) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
4
4
 
5
- Simple bridge between `koa-session` and `socket.io`. Shares a unified session across HTTP and WebSocket using a session store.
5
+ Bridge between `koa-session` and `socket.io` with one shared session store.
6
+
7
+ You get:
8
+ - standard `ctx.session` for HTTP
9
+ - `socket.sessionId` and `socket.withSession(...)` for WebSocket
10
+ - a clear destroy flow for stale sockets
6
11
 
7
12
  ---
8
13
 
@@ -14,51 +19,227 @@ npm i @randajan/koa-io-session
14
19
 
15
20
  ---
16
21
 
17
- ## Quick use
22
+ ## Use case
23
+
24
+ Typical scenario:
25
+ 1. User signs in via HTTP (`ctx.session.userId = ...`).
26
+ 2. Client opens Socket.IO connection.
27
+ 3. Socket handlers read/update the same session as HTTP handlers.
28
+ 4. Session reset/logout invalidates related sockets.
29
+
30
+ This is useful for realtime apps where HTTP and WS must stay consistent without duplicate auth/session logic.
31
+
32
+ ---
33
+
34
+ ## Quick start
18
35
 
19
36
  ```js
20
37
  import Koa from "koa";
21
- import http from "http";
38
+ import { createServer } from "http";
22
39
  import { Server } from "socket.io";
23
40
  import { attachSession } from "@randajan/koa-io-session";
24
41
 
25
42
  const app = new Koa();
26
- const server = http.createServer(app.callback());
27
- const io = new Server(server);
43
+ const http = createServer(app.callback());
44
+ const io = new Server(http, {
45
+ cors: { origin: true, credentials: true }
46
+ });
28
47
 
29
48
  const store = attachSession(app, io, {
30
- key: "koa:sess",
49
+ key: "koa.io.sid",
31
50
  signed: true,
32
- maxAge: 86400000
51
+ maxAge: 24 * 60 * 60 * 1000,
52
+ sameSite: "lax",
53
+ httpOnly: true
54
+ });
55
+
56
+ // Required: cleanup is manual
57
+ store.autoCleanup();
58
+
59
+ // HTTP route example
60
+ app.use(async (ctx, next) => {
61
+ if (ctx.path !== "/api/session") { return next(); }
62
+
63
+ const action = String(ctx.query.action || "read");
64
+ if (action === "reset") {
65
+ ctx.session = null;
66
+ ctx.body = { ok: true, from: "http:reset" };
67
+ return;
68
+ }
69
+
70
+ if (!ctx.session.createdAt) { ctx.session.createdAt = Date.now(); }
71
+ if (!Number.isFinite(ctx.session.httpCount)) { ctx.session.httpCount = 0; }
72
+ if (action === "inc") { ctx.session.httpCount += 1; }
73
+
74
+ ctx.body = {
75
+ ok: true,
76
+ from: `http:${action}`,
77
+ sessionId: ctx.sessionId,
78
+ session: ctx.session
79
+ };
80
+ });
81
+
82
+ io.on("connection", (socket) => {
83
+ socket.on("session:get", async (ack) => {
84
+ try {
85
+ const payload = await socket.withSession(async (sessionCtx) => {
86
+ return {
87
+ ok: true,
88
+ from: "ws:get",
89
+ sessionId: sessionCtx.sessionId,
90
+ session: sessionCtx.session
91
+ };
92
+ });
93
+ if (typeof ack === "function") { ack(payload); }
94
+ } catch (error) {
95
+ if (typeof ack === "function") {
96
+ ack({ ok: false, from: "ws:get", error: error.message });
97
+ }
98
+ }
99
+ });
100
+ });
101
+
102
+ http.listen(3000);
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Socket API
108
+
109
+ After `attachSession(app, io, opt)`:
110
+
111
+ 1. `socket.sessionId`
112
+ - Session ID resolved from cookie/external key during socket middleware.
113
+
114
+ 2. `socket.withSession(handler)`
115
+ - Safe wrapper for session operations with per-session lock.
116
+ - `handler` receives `sessionCtx`:
117
+ - `sessionCtx.sessionId` -> session ID
118
+ - `sessionCtx.session` -> mutable session object
119
+ - `sessionCtx.socket` -> socket instance
120
+ - Return value of `handler` is returned by `withSession`.
121
+
122
+ Example:
123
+
124
+ ```js
125
+ const result = await socket.withSession(async (sessionCtx) => {
126
+ sessionCtx.session.wsCount = (sessionCtx.session.wsCount || 0) + 1;
127
+ return { ok: true, sid: sessionCtx.sessionId };
33
128
  });
129
+ ```
130
+
131
+ Destroy session from WS:
34
132
 
35
- io.on("connection", socket => {
36
- console.log("session ID:", socket.sessionId);
37
- console.log("session data:", socket.session);
133
+ ```js
134
+ await socket.withSession(async (sessionCtx) => {
135
+ sessionCtx.session = null;
38
136
  });
39
137
  ```
40
138
 
41
139
  ---
42
140
 
43
- ## Socket helpers
141
+ ## Session model behavior
142
+
143
+ 1. WS does not create missing sessions.
144
+ - If session is missing in store, `withSession` throws `Session not found`.
145
+ - New session must be created via HTTP/Koa flow.
146
+
147
+ 2. Change detection:
148
+ - If session changed, store `set` is called.
149
+ - If session did not change, throttled `touch` is used to refresh TTL.
150
+
151
+ 3. Concurrency:
152
+ - Operations for same `sessionId` are serialized to avoid race conditions.
153
+
154
+ ---
155
+
156
+ ## Destroy flow
157
+
158
+ When store emits `destroy` for a SID:
159
+ 1. all sockets in room `sessionId:<sid>` receive `session:destroy`
160
+ 2. after ~200 ms, these sockets are forcibly disconnected
161
+
162
+ Client should react to `session:destroy` by:
163
+ 1. performing HTTP bootstrap request (to get new cookie/session)
164
+ 2. reconnecting socket
165
+
166
+ ---
167
+
168
+ ## Reserved names and limitations
169
+
170
+ 1. Room naming:
171
+ - library joins sockets into room `sessionId:<sid>`
172
+
173
+ 2. Reserved socket event:
174
+ - library emits `session:destroy`
175
+
176
+ 3. Reserved socket properties:
177
+ - library defines `socket.sessionId` and `socket.withSession`
178
+
179
+ 4. WS cannot bootstrap missing session:
180
+ - when session is missing in store, `withSession` throws `Session not found`
181
+ - new session must be created via HTTP/Koa flow
182
+
183
+ 5. Session ID is fixed for current connection:
184
+ - if cookie/session changes, existing socket must reconnect to use new SID
185
+
186
+ ---
187
+
188
+ ## Required integration details
44
189
 
45
- - `socket.sessionId` session ID from cookies
46
- - `socket.session` → session object from store
190
+ 1. Cleanup is manual:
191
+ - call `store.autoCleanup(interval)` yourself
192
+
193
+ 2. Custom store API is required:
194
+ - `get(sid)`
195
+ - `set(sid, session, maxAge)`
196
+ - `destroy(sid)`
197
+ - `touch(sid, maxAge)`
198
+ - `on(eventName, callback)`
199
+
200
+ 3. Store must emit destroy event:
201
+ - event name: `destroy`
202
+ - callback signature expected by this library: `(_store, sid) => {}`
203
+
204
+ 4. Destroy socket behavior:
205
+ - library emits `session:destroy`
206
+ - library disconnects socket room ~200 ms later
47
207
 
48
208
  ---
49
209
 
50
- ## Production notes
210
+ ## Options
211
+
212
+ `attachSession(app, io, opt)` forwards session options to `koa-session`.
51
213
 
52
- - **Stable signing keys**: Provide your own `app.keys` and a fixed `opt.key` (cookie name).
53
- Using randomly generated values on every server restart will invalidate existing signed cookies and force users to log in again.
54
- - **Persistent stores for production**: The bundled in‑memory store works only for local development because all sessions disappear when the process restarts.
55
- Configure a persistent store such as Redis, DynamoDB, or SQL for real deployments.
56
- - **Proxy deep‑mutation limitation**: The session proxy tracks changes only on top‑level properties.
57
- If you mutate nested objects you must either replace the whole object or use immutable updates so that changes are picked up and persisted.
58
- - **Middleware order matters**: Call `attachSession` *before* any middleware (Router, authentication, etc.) that expects `ctx.session` to exist.
214
+ Defaults set by this library when missing:
215
+ - `opt.signed = true`
216
+ - `opt.key = generateUid(12)`
217
+ - `opt.maxAge = 86_400_000`
218
+ - `app.keys` auto-generated if missing
59
219
 
220
+ Optional:
221
+ - `opt.store` custom store implementing required API above
222
+ - `opt.externalKey` works as in `koa-session`
60
223
 
224
+ ---
225
+
226
+ ## Exports
227
+
228
+ ```js
229
+ import attachSession, { attachSession, SessionStore, generateUid } from "@randajan/koa-io-session";
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Production checklist
235
+
236
+ 1. Set stable `app.keys` and fixed cookie `opt.key`.
237
+ 2. Use persistent store (Redis/DB), not in-memory store.
238
+ 3. Call `store.autoCleanup(...)`.
239
+ 4. Handle `session:destroy` on client and reconnect via HTTP bootstrap.
240
+
241
+ ---
61
242
 
62
243
  ## License
63
244
 
64
- MIT © [randajan](https://github.com/randajan)
245
+ MIT (c) [randajan](https://github.com/randajan)
@@ -37,97 +37,290 @@ __export(index_exports, {
37
37
  module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/attachSession.js
40
+ var import_http = require("http");
40
41
  var import_koa_session = __toESM(require("koa-session"), 1);
41
- var import_props2 = require("@randajan/props");
42
+ var import_props3 = require("@randajan/props");
42
43
 
43
- // src/uid.js
44
+ // src/tools.js
44
45
  var import_crypto = __toESM(require("crypto"), 1);
45
46
  var generateUid = (len = 16) => import_crypto.default.randomBytes(len).toString("base64url").slice(0, len);
47
+ var isObject = (value) => !!value && typeof value === "object";
46
48
 
47
49
  // src/SessionStore.js
48
50
  var import_props = require("@randajan/props");
49
- var SessionStore = class {
50
- constructor(defaultTTL = 864e5) {
51
- (0, import_props.solid)(this, "_data", /* @__PURE__ */ new Map());
52
- (0, import_props.solid)(this, "_defaultTTL", defaultTTL);
51
+ var import_events = require("events");
52
+ var wrapStore = (store) => {
53
+ return {
54
+ get: store.get.bind(store),
55
+ set: store.set.bind(store),
56
+ destroy: store.destroy.bind(store),
57
+ touch: store.touch.bind(store)
58
+ };
59
+ };
60
+ var formatState = (session2, maxAge, prevTTL, defaultTTL) => {
61
+ const ttl = maxAge ?? prevTTL ?? defaultTTL;
62
+ const expiresAt = Date.now() + ttl;
63
+ return { session: session2, expiresAt, ttl };
64
+ };
65
+ var SessionStore = class extends Map {
66
+ constructor(defaultTTL = 864e5, eventEmitterOpt = {}) {
67
+ super();
68
+ (0, import_props.solid)(this, "defaultTTL", defaultTTL);
69
+ (0, import_props.solid)(this, "event", new import_events.EventEmitter(eventEmitterOpt));
70
+ }
71
+ on(eventName, callback) {
72
+ return this.event.on(eventName, callback);
53
73
  }
54
74
  get(sid) {
55
- const d = this._data.get(sid);
56
- if (!d) return;
75
+ const d = super.get(sid);
76
+ if (!d) {
77
+ return;
78
+ }
57
79
  if (Date.now() < d.expiresAt) {
58
80
  return d.session;
59
81
  }
60
- this.destroy(sid);
61
- return {};
82
+ this.delete(sid);
83
+ }
84
+ touch(sid, maxAge) {
85
+ const { defaultTTL } = this;
86
+ const d = super.get(sid);
87
+ if (!d) {
88
+ return false;
89
+ }
90
+ super.set(sid, formatState(d.session, maxAge, d.ttl, defaultTTL));
91
+ this.event.emit("touch", this, sid);
92
+ return true;
62
93
  }
63
94
  set(sid, session2, maxAge) {
64
- const { _data, _defaultTTL } = this;
65
- const d = _data.get(sid);
66
- const ttl = maxAge ?? d?.ttl ?? _defaultTTL;
67
- const expiresAt = Date.now() + ttl;
68
- _data.set(sid, { session: session2, expiresAt, ttl });
95
+ const { defaultTTL } = this;
96
+ const d = super.get(sid);
97
+ if (session2 == null) {
98
+ return !d || this.destroy(sid);
99
+ }
100
+ super.set(sid, formatState(session2, maxAge, d?.ttl, defaultTTL));
101
+ this.event.emit("set", this, sid, !d);
102
+ return true;
103
+ }
104
+ delete(sid) {
105
+ return this.destroy(sid);
69
106
  }
70
107
  destroy(sid) {
71
- this._data.delete(sid);
108
+ if (this.has(sid)) {
109
+ super.delete(sid);
110
+ this.event.emit("destroy", this, sid);
111
+ }
112
+ return true;
72
113
  }
73
114
  cleanup() {
74
- const { _data } = this;
75
115
  const now = Date.now();
76
116
  let cleared = 0;
77
- for (const [sid, d] of _data) {
117
+ for (const [sid, d] of this.entries()) {
78
118
  if (now < d.expiresAt) {
79
119
  continue;
80
120
  }
81
- _data.delete(sid);
82
- cleared++;
121
+ if (this.destroy(sid)) {
122
+ cleared++;
123
+ }
124
+ }
125
+ if (cleared) {
126
+ this.event.emit("cleanup", this, cleared);
83
127
  }
84
128
  return cleared;
85
129
  }
86
- autoCleanup(interval = 36e5, onCleanup = () => {
87
- }) {
130
+ autoCleanup(interval) {
131
+ const { defaultTTL } = this;
132
+ if (!interval) {
133
+ interval = defaultTTL / 10;
134
+ }
88
135
  const tid = setInterval(() => {
89
- const cleared = this.cleanup();
90
- if (cleared) {
91
- onCleanup(cleared);
92
- }
136
+ this.cleanup();
93
137
  }, interval);
94
138
  return (_) => clearInterval(tid);
95
139
  }
96
140
  };
97
141
 
142
+ // src/socketSession.js
143
+ var import_props2 = require("@randajan/props");
144
+ var import_queue = require("@randajan/queue");
145
+ var sidLocks = /* @__PURE__ */ new Map();
146
+ var touchQueues = /* @__PURE__ */ new Map();
147
+ var createSessionCtx = (sessionId, session2, socket) => (0, import_props2.solids)({ session: session2 }, { sessionId, socket });
148
+ var createSessionHash = (session2) => {
149
+ try {
150
+ return JSON.stringify(session2 ?? null);
151
+ } catch {
152
+ return null;
153
+ }
154
+ };
155
+ var isSessionHashChanged = (originalHash, session2) => {
156
+ const nextHash = createSessionHash(session2);
157
+ if (originalHash == null || nextHash == null) {
158
+ return true;
159
+ }
160
+ return originalHash !== nextHash;
161
+ };
162
+ var withLock = async (task, socket, ...args) => {
163
+ const sid = socket.sessionId;
164
+ const previous = sidLocks.get(sid);
165
+ let releaseCurrent;
166
+ const current = new Promise((resolve) => {
167
+ releaseCurrent = resolve;
168
+ });
169
+ sidLocks.set(sid, current);
170
+ if (previous) {
171
+ await previous;
172
+ }
173
+ try {
174
+ return await task(socket, ...args);
175
+ } finally {
176
+ releaseCurrent();
177
+ if (sidLocks.get(sid) === current) {
178
+ sidLocks.delete(sid);
179
+ }
180
+ }
181
+ };
182
+ var getTouchQueue = (sid, store) => {
183
+ const existing = touchQueues.get(sid);
184
+ if (existing) {
185
+ return existing;
186
+ }
187
+ const queue = (0, import_queue.createQueue)(async (touchMaxAge) => {
188
+ try {
189
+ await store.touch(sid, touchMaxAge);
190
+ } catch {
191
+ }
192
+ if (!queue.isPending) {
193
+ touchQueues.delete(sid);
194
+ }
195
+ }, { pass: "last", softMs: 1e3, hardMs: 5e3 });
196
+ touchQueues.set(sid, queue);
197
+ return queue;
198
+ };
199
+ var scheduleTouch = (sid, store, maxAge) => {
200
+ if (typeof store?.touch !== "function") {
201
+ return;
202
+ }
203
+ const queue = getTouchQueue(sid, store);
204
+ queue(maxAge);
205
+ };
206
+ var clearTouchQueue = (sid) => {
207
+ const queue = touchQueues.get(sid);
208
+ if (!queue) {
209
+ return false;
210
+ }
211
+ queue.flush();
212
+ touchQueues.delete(sid);
213
+ return true;
214
+ };
215
+ var runSessionHandler = async (socket, handler, opt = {}) => {
216
+ const sid = socket.sessionId;
217
+ const { store, maxAge } = opt;
218
+ const current = await store.get(sid);
219
+ if (!isObject(current)) {
220
+ throw new Error("Session not found");
221
+ }
222
+ const session2 = current;
223
+ const sessionCtx = createSessionCtx(sid, session2, socket);
224
+ const originalHash = createSessionHash(sessionCtx.session);
225
+ const result = await handler(sessionCtx, socket);
226
+ if (sessionCtx.session == null) {
227
+ clearTouchQueue(sid);
228
+ await store.destroy(sid);
229
+ return result;
230
+ }
231
+ if (!isObject(sessionCtx.session)) {
232
+ throw new TypeError("sessionCtx.session must be an object or null");
233
+ }
234
+ if (isSessionHashChanged(originalHash, sessionCtx.session)) {
235
+ clearTouchQueue(sid);
236
+ await store.set(sid, sessionCtx.session, maxAge);
237
+ } else {
238
+ scheduleTouch(sid, store, maxAge);
239
+ }
240
+ return result;
241
+ };
242
+ var applySessionHandler = async (socket, handler, opt = {}) => {
243
+ if (typeof handler !== "function") {
244
+ throw new TypeError("socket.withSession(handler) requires a function");
245
+ }
246
+ if (!socket.sessionId) {
247
+ throw new Error("Missing session id");
248
+ }
249
+ return withLock(runSessionHandler, socket, handler, opt);
250
+ };
251
+
98
252
  // src/attachSession.js
253
+ var validateStore = (store) => {
254
+ const missing = [];
255
+ if (typeof store?.get !== "function") {
256
+ missing.push("get()");
257
+ }
258
+ if (typeof store?.set !== "function") {
259
+ missing.push("set()");
260
+ }
261
+ if (typeof store?.destroy !== "function") {
262
+ missing.push("destroy()");
263
+ }
264
+ if (typeof store?.touch !== "function") {
265
+ missing.push("touch()");
266
+ }
267
+ if (typeof store?.on !== "function") {
268
+ missing.push("on()");
269
+ }
270
+ if (missing.length) {
271
+ throw new TypeError(`attachSession options.store is missing required API: ${missing.join(", ")}`);
272
+ }
273
+ };
99
274
  var attachSession = (app, io, opt = {}) => {
100
- const signed = "signed" in opt ? !!opt.signed : true;
101
- delete opt.signed;
102
- if (!app.keys) app.keys = Array(6).fill().map(() => generateUid(12));
103
- if (!opt.key) opt.key = generateUid(12);
275
+ opt.signed = "signed" in opt ? !!opt.signed : true;
276
+ if (!app.keys) {
277
+ app.keys = Array(6).fill().map(() => generateUid(12));
278
+ }
279
+ if (!opt.key) {
280
+ opt.key = generateUid(12);
281
+ }
104
282
  if (!opt.maxAge) {
105
283
  opt.maxAge = 864e5;
106
284
  }
107
- if (!opt.store) {
108
- opt.store = new SessionStore(opt.maxAge);
109
- }
110
- const { key, store } = opt;
285
+ const { key, signed, externalKey } = opt;
286
+ const store = opt.store || new SessionStore(opt.maxAge);
287
+ validateStore(store);
288
+ opt.store = wrapStore(store);
289
+ const getSID = externalKey ? (ctx) => externalKey.get(ctx) : (ctx) => ctx.cookies.get(key, { signed });
111
290
  const koaSession = (0, import_koa_session.default)(opt, app);
112
291
  app.use(koaSession);
113
292
  app.use(async (ctx, next) => {
114
- ctx.session.active = true;
115
- (0, import_props2.solid)(ctx, "sessionId", ctx.cookies.get(key, { signed }));
293
+ (0, import_props3.solid)(ctx, "sessionId", getSID(ctx));
116
294
  await next();
117
295
  });
118
296
  io.use(async (socket, next) => {
119
- if (!socket.request.headers.cookie) {
120
- return next(new Error("No cookie"));
121
- }
122
- const ctx = app.createContext(socket.request, socket.response);
297
+ const req = socket.request;
298
+ const res = req.res ?? socket.response ?? new import_http.ServerResponse(req);
299
+ const ctx = app.createContext(req, res);
123
300
  await koaSession(ctx, async () => {
124
301
  });
125
- const sid = ctx.cookies.get(key, { signed });
126
- (0, import_props2.solid)(socket, "sessionId", sid);
127
- (0, import_props2.virtual)(socket, "session", (_) => store.get(sid));
302
+ (0, import_props3.solid)(socket, "sessionId", getSID(ctx));
303
+ (0, import_props3.solid)(socket, "withSession", async (handler) => {
304
+ return applySessionHandler(socket, handler, opt);
305
+ }, false);
128
306
  await next();
129
307
  });
130
- return koaSession.store;
308
+ io.on("connection", (socket) => {
309
+ const sid = socket.sessionId;
310
+ if (sid) {
311
+ socket.join(`sessionId:${sid}`);
312
+ }
313
+ });
314
+ store.on("destroy", (_store, sid) => {
315
+ if (!sid) {
316
+ return;
317
+ }
318
+ clearTouchQueue(sid);
319
+ const room = io.in(`sessionId:${sid}`);
320
+ room.emit("session:destroy");
321
+ setTimeout((_) => room.disconnectSockets(true), 200);
322
+ });
323
+ return store;
131
324
  };
132
325
 
133
326
  // src/index.js
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/index.js", "../../src/attachSession.js", "../../src/uid.js", "../../src/SessionStore.js"],
4
- "sourcesContent": ["import { attachSession } from \"./attachSession\";\r\nimport { SessionStore } from \"./SessionStore\";\r\nimport { generateUid } from \"./uid\";\r\n\r\n\r\nexport default attachSession;\r\n\r\nexport {\r\n attachSession,\r\n generateUid,\r\n SessionStore\r\n}", "// attachSession.js \u2013 verze s auto-persist proxy\nimport session from \"koa-session\";\nimport { solid, virtual } from \"@randajan/props\";\nimport { generateUid } from \"./uid\";\nimport { SessionStore } from \"./SessionStore\";\n\n\nexport const attachSession = (app, io, opt = {}) => {\n const signed = \"signed\" in opt ? !!opt.signed : true;\n delete opt.signed;\n\n\n if (!app.keys) app.keys = Array(6).fill().map(() => generateUid(12));\n if (!opt.key) opt.key = generateUid(12);\n if (!opt.maxAge) { opt.maxAge = 86_400_000; }\n if (!opt.store) { opt.store = new SessionStore(opt.maxAge); }\n\n const { key, store } = opt;\n\n const koaSession = session(opt, app);\n app.use(koaSession);\n\n // pro HTTP jen sessionId, nic v\u00EDc nepot\u0159ebujeme\n app.use(async (ctx, next) => {\n ctx.session.active = true;\n solid(ctx, \"sessionId\", ctx.cookies.get(key, { signed }));\n await next();\n });\n\n /* ------------------ WebSocket ------------------------------------- */\n io.use(async (socket, next) => {\n\n if (!socket.request.headers.cookie) { return next(new Error(\"No cookie\")); }\n\n const ctx = app.createContext(socket.request, socket.response);\n await koaSession(ctx, async () => { }); // aktivuj koa-session\n\n const sid = ctx.cookies.get(key, { signed });\n\n solid(socket, \"sessionId\", sid);\n virtual(socket, \"session\", _=>store.get(sid)); // <-- u\u017E se ukl\u00E1d\u00E1 samo\n\n await next();\n });\n\n return koaSession.store; // kdyby ses k n\u011Bmu cht\u011Bl dostat jinde\n};", "import crypto from \"crypto\";\r\n\r\nexport const generateUid = (len = 16) => crypto.randomBytes(len).toString(\"base64url\").slice(0, len);", "import { solid } from \"@randajan/props\";\r\n\r\nexport class SessionStore {\r\n constructor(defaultTTL=86_400_000) {\r\n solid(this, \"_data\", new Map());\r\n solid(this, \"_defaultTTL\", defaultTTL);\r\n }\r\n\r\n get(sid) {\r\n const d = this._data.get(sid);\r\n if (!d) return;\r\n if (Date.now() < d.expiresAt) { return d.session; }\r\n this.destroy(sid);\r\n return {};\r\n }\r\n\r\n set(sid, session, maxAge) {\r\n const { _data, _defaultTTL } = this;\r\n const d = _data.get(sid);\r\n const ttl = maxAge ?? d?.ttl ?? _defaultTTL;\r\n const expiresAt = Date.now() + ttl;\r\n _data.set(sid, { session, expiresAt, ttl });\r\n }\r\n\r\n destroy(sid) { this._data.delete(sid); }\r\n\r\n cleanup() {\r\n const { _data } = this;\r\n\r\n const now = Date.now();\r\n let cleared = 0;\r\n\r\n for (const [sid, d] of _data) {\r\n if (now < d.expiresAt) { continue; }\r\n _data.delete(sid);\r\n cleared++;\r\n }\r\n\r\n return cleared;\r\n }\r\n\r\n autoCleanup(interval=3_600_000, onCleanup=()=>{}) {\r\n const tid = setInterval(() => {\r\n const cleared = this.cleanup();\r\n if (cleared) { onCleanup(cleared); }\r\n }, interval);\r\n return _ => clearInterval(tid);\r\n }\r\n}"],
5
- "mappings": "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",
6
- "names": ["import_props", "crypto", "session", "session"]
3
+ "sources": ["../../src/index.js", "../../src/attachSession.js", "../../src/tools.js", "../../src/SessionStore.js", "../../src/socketSession.js"],
4
+ "sourcesContent": ["import { attachSession } from \"./attachSession.js\";\nimport { SessionStore } from \"./SessionStore.js\";\nimport { generateUid } from \"./tools.js\";\n\n\nexport default attachSession;\n\nexport {\n attachSession,\n generateUid,\n SessionStore\n}\n", "// attachSession.js \u2013 verze s auto-persist proxy\nimport { ServerResponse } from \"http\";\nimport session from \"koa-session\";\nimport { solid } from \"@randajan/props\";\nimport { generateUid } from \"./tools.js\";\nimport { SessionStore, wrapStore } from \"./SessionStore.js\";\nimport { applySessionHandler, clearTouchQueue } from \"./socketSession.js\";\n\n\nconst validateStore = (store) => {\n const missing = [];\n if (typeof store?.get !== \"function\") { missing.push(\"get()\"); }\n if (typeof store?.set !== \"function\") { missing.push(\"set()\"); }\n if (typeof store?.destroy !== \"function\") { missing.push(\"destroy()\"); }\n if (typeof store?.touch !== \"function\") { missing.push(\"touch()\"); }\n if (typeof store?.on!== \"function\") { missing.push(\"on()\"); }\n\n if (missing.length) {\n throw new TypeError(`attachSession options.store is missing required API: ${missing.join(\", \")}`);\n }\n};\n\n\nexport const attachSession = (app, io, opt = {}) => {\n opt.signed = \"signed\" in opt ? !!opt.signed : true;\n\n if (!app.keys) { app.keys = Array(6).fill().map(() => generateUid(12)); }\n if (!opt.key) { opt.key = generateUid(12); }\n if (!opt.maxAge) { opt.maxAge = 86_400_000; }\n\n const { key, signed, externalKey } = opt;\n const store = opt.store || new SessionStore(opt.maxAge);\n validateStore(store);\n opt.store = wrapStore(store);\n\n const getSID = externalKey ? ctx=>externalKey.get(ctx) : ctx=>ctx.cookies.get(key, { signed });\n\n const koaSession = session(opt, app);\n app.use(koaSession);\n\n // pro HTTP jen sessionId, nic v\u00EDc nepot\u0159ebujeme\n app.use(async (ctx, next) => {\n solid(ctx, \"sessionId\", getSID(ctx));\n await next();\n });\n\n /* ------------------ WebSocket ------------------------------------- */\n io.use(async (socket, next) => {\n const req = socket.request;\n const res = req.res ?? socket.response ?? new ServerResponse(req);\n const ctx = app.createContext(req, res);\n\n await koaSession(ctx, async () => {}); //aktivuje session\n \n solid(socket, \"sessionId\", getSID(ctx));\n solid(socket, \"withSession\", async (handler)=>{\n return applySessionHandler(socket, handler, opt);\n }, false);\n\n await next();\n });\n\n io.on(\"connection\", socket=>{\n const sid = socket.sessionId;\n if (sid) { socket.join(`sessionId:${sid}`); }\n });\n\n store.on(\"destroy\", (_store, sid)=>{\n if (!sid) { return; }\n clearTouchQueue(sid);\n const room = io.in(`sessionId:${sid}`);\n room.emit(\"session:destroy\");\n setTimeout(_=>room.disconnectSockets(true), 200);\n });\n\n return store;\n};\n", "import crypto from \"crypto\";\r\n\r\nexport const generateUid = (len = 16) => crypto.randomBytes(len).toString(\"base64url\").slice(0, len);\r\n\r\nexport const isObject = (value) => !!value && typeof value === \"object\";", "import { solid } from \"@randajan/props\";\r\nimport { EventEmitter } from \"events\";\r\n\r\n\r\n\r\nexport const wrapStore = (store)=>{\r\n return {\r\n get:store.get.bind(store),\r\n set:store.set.bind(store),\r\n destroy:store.destroy.bind(store),\r\n touch:store.touch.bind(store)\r\n }\r\n}\r\n\r\nconst formatState = (session, maxAge, prevTTL, defaultTTL)=>{\r\n const ttl = maxAge ?? prevTTL ?? defaultTTL;\r\n const expiresAt = Date.now() + ttl;\r\n return { session, expiresAt, ttl };\r\n}\r\n\r\nexport class SessionStore extends Map {\r\n\r\n constructor(defaultTTL=86_400_000, eventEmitterOpt={}) {\r\n super();\r\n solid(this, \"defaultTTL\", defaultTTL);\r\n solid(this, \"event\", new EventEmitter(eventEmitterOpt));\r\n }\r\n\r\n on(eventName, callback) {\r\n return this.event.on(eventName, callback);\r\n }\r\n\r\n get(sid) {\r\n const d = super.get(sid);\r\n if (!d) { return; }\r\n if (Date.now() < d.expiresAt) { return d.session; }\r\n this.delete(sid);\r\n }\r\n\r\n touch(sid, maxAge) {\r\n const { defaultTTL } = this;\r\n const d = super.get(sid);\r\n if (!d) { return false; }\r\n super.set(sid, formatState(d.session, maxAge, d.ttl, defaultTTL));\r\n this.event.emit(\"touch\", this, sid);\r\n return true;\r\n }\r\n\r\n set(sid, session, maxAge) {\r\n const { defaultTTL } = this;\r\n const d = super.get(sid);\r\n if (session == null) { return !d || this.destroy(sid); }\r\n super.set(sid, formatState(session, maxAge, d?.ttl, defaultTTL));\r\n this.event.emit(\"set\", this, sid, !d);\r\n return true;\r\n }\r\n\r\n delete(sid) {\r\n return this.destroy(sid);\r\n }\r\n\r\n destroy(sid) {\r\n if (this.has(sid)) {\r\n super.delete(sid);\r\n this.event.emit(\"destroy\", this, sid);\r\n }\r\n return true;\r\n }\r\n\r\n cleanup() {\r\n const now = Date.now();\r\n let cleared = 0;\r\n\r\n for (const [sid, d] of this.entries()) {\r\n if (now < d.expiresAt) { continue; }\r\n if (this.destroy(sid)) { cleared++; }\r\n }\r\n\r\n if (cleared) { this.event.emit(\"cleanup\", this, cleared); }\r\n\r\n return cleared;\r\n }\r\n\r\n autoCleanup(interval) {\r\n const { defaultTTL } = this;\r\n if (!interval) { interval = defaultTTL/10; }\r\n\r\n const tid = setInterval(() => {\r\n this.cleanup();\r\n }, interval);\r\n return _ => clearInterval(tid);\r\n }\r\n}\r\n", "import { solids } from \"@randajan/props\";\nimport { createQueue } from \"@randajan/queue\";\nimport { isObject } from \"./tools\";\n\n\nconst sidLocks = new Map();\nconst touchQueues = new Map();\n\nconst createSessionCtx = (sessionId, session, socket) =>solids({ session }, { sessionId, socket });\n\nconst createSessionHash = (session) => {\n try { return JSON.stringify(session ?? null); }\n catch { return null; }\n};\n\nconst isSessionHashChanged = (originalHash, session) => {\n const nextHash = createSessionHash(session);\n if (originalHash == null || nextHash == null) { return true; }\n return originalHash !== nextHash;\n};\n\nconst withLock = async (task, socket, ...args) => {\n const sid = socket.sessionId;\n const previous = sidLocks.get(sid);\n let releaseCurrent;\n const current = new Promise((resolve) => { releaseCurrent = resolve; });\n sidLocks.set(sid, current);\n\n if (previous) { await previous; }\n\n try {\n return await task(socket, ...args);\n } finally {\n releaseCurrent();\n if (sidLocks.get(sid) === current) { sidLocks.delete(sid); }\n }\n};\n\nconst getTouchQueue = (sid, store) => {\n const existing = touchQueues.get(sid);\n if (existing) { return existing; }\n\n const queue = createQueue(async (touchMaxAge) => {\n try { await store.touch(sid, touchMaxAge); } catch {}\n if (!queue.isPending) { touchQueues.delete(sid); }\n }, { pass: \"last\", softMs:1_000, hardMs:5_000 });\n\n touchQueues.set(sid, queue);\n return queue;\n};\n\nconst scheduleTouch = (sid, store, maxAge) => {\n if (typeof store?.touch !== \"function\") { return; }\n const queue = getTouchQueue(sid, store);\n queue(maxAge);\n};\n\nexport const clearTouchQueue = (sid) => {\n const queue = touchQueues.get(sid);\n if (!queue) { return false; }\n queue.flush();\n touchQueues.delete(sid);\n return true;\n};\n\nconst runSessionHandler = async (socket, handler, opt={}) => {\n const sid = socket.sessionId;\n const { store, maxAge } = opt;\n const current = await store.get(sid);\n\n if (!isObject(current)) { throw new Error(\"Session not found\"); }\n const session = current;\n const sessionCtx = createSessionCtx(sid, session, socket);\n\n const originalHash = createSessionHash(sessionCtx.session);\n const result = await handler(sessionCtx, socket);\n\n if (sessionCtx.session == null) {\n clearTouchQueue(sid);\n await store.destroy(sid);\n return result;\n }\n\n if (!isObject(sessionCtx.session)) {\n throw new TypeError(\"sessionCtx.session must be an object or null\");\n }\n\n if (isSessionHashChanged(originalHash, sessionCtx.session)) {\n clearTouchQueue(sid);\n await store.set(sid, sessionCtx.session, maxAge);\n } else {\n scheduleTouch(sid, store, maxAge);\n }\n\n return result;\n};\n\nexport const applySessionHandler = async (socket, handler, opt={}) => {\n\n if (typeof handler !== \"function\") {\n throw new TypeError(\"socket.withSession(handler) requires a function\");\n }\n if (!socket.sessionId) {\n throw new Error(\"Missing session id\");\n }\n\n return withLock(runSessionHandler, socket, handler, opt);\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["import_props", "crypto", "session", "import_props", "session", "session"]
7
7
  }
@@ -1,95 +1,288 @@
1
1
  // src/attachSession.js
2
+ import { ServerResponse } from "http";
2
3
  import session from "koa-session";
3
- import { solid as solid2, virtual } from "@randajan/props";
4
+ import { solid as solid2 } from "@randajan/props";
4
5
 
5
- // src/uid.js
6
+ // src/tools.js
6
7
  import crypto from "crypto";
7
8
  var generateUid = (len = 16) => crypto.randomBytes(len).toString("base64url").slice(0, len);
9
+ var isObject = (value) => !!value && typeof value === "object";
8
10
 
9
11
  // src/SessionStore.js
10
12
  import { solid } from "@randajan/props";
11
- var SessionStore = class {
12
- constructor(defaultTTL = 864e5) {
13
- solid(this, "_data", /* @__PURE__ */ new Map());
14
- solid(this, "_defaultTTL", defaultTTL);
13
+ import { EventEmitter } from "events";
14
+ var wrapStore = (store) => {
15
+ return {
16
+ get: store.get.bind(store),
17
+ set: store.set.bind(store),
18
+ destroy: store.destroy.bind(store),
19
+ touch: store.touch.bind(store)
20
+ };
21
+ };
22
+ var formatState = (session2, maxAge, prevTTL, defaultTTL) => {
23
+ const ttl = maxAge ?? prevTTL ?? defaultTTL;
24
+ const expiresAt = Date.now() + ttl;
25
+ return { session: session2, expiresAt, ttl };
26
+ };
27
+ var SessionStore = class extends Map {
28
+ constructor(defaultTTL = 864e5, eventEmitterOpt = {}) {
29
+ super();
30
+ solid(this, "defaultTTL", defaultTTL);
31
+ solid(this, "event", new EventEmitter(eventEmitterOpt));
32
+ }
33
+ on(eventName, callback) {
34
+ return this.event.on(eventName, callback);
15
35
  }
16
36
  get(sid) {
17
- const d = this._data.get(sid);
18
- if (!d) return;
37
+ const d = super.get(sid);
38
+ if (!d) {
39
+ return;
40
+ }
19
41
  if (Date.now() < d.expiresAt) {
20
42
  return d.session;
21
43
  }
22
- this.destroy(sid);
23
- return {};
44
+ this.delete(sid);
45
+ }
46
+ touch(sid, maxAge) {
47
+ const { defaultTTL } = this;
48
+ const d = super.get(sid);
49
+ if (!d) {
50
+ return false;
51
+ }
52
+ super.set(sid, formatState(d.session, maxAge, d.ttl, defaultTTL));
53
+ this.event.emit("touch", this, sid);
54
+ return true;
24
55
  }
25
56
  set(sid, session2, maxAge) {
26
- const { _data, _defaultTTL } = this;
27
- const d = _data.get(sid);
28
- const ttl = maxAge ?? d?.ttl ?? _defaultTTL;
29
- const expiresAt = Date.now() + ttl;
30
- _data.set(sid, { session: session2, expiresAt, ttl });
57
+ const { defaultTTL } = this;
58
+ const d = super.get(sid);
59
+ if (session2 == null) {
60
+ return !d || this.destroy(sid);
61
+ }
62
+ super.set(sid, formatState(session2, maxAge, d?.ttl, defaultTTL));
63
+ this.event.emit("set", this, sid, !d);
64
+ return true;
65
+ }
66
+ delete(sid) {
67
+ return this.destroy(sid);
31
68
  }
32
69
  destroy(sid) {
33
- this._data.delete(sid);
70
+ if (this.has(sid)) {
71
+ super.delete(sid);
72
+ this.event.emit("destroy", this, sid);
73
+ }
74
+ return true;
34
75
  }
35
76
  cleanup() {
36
- const { _data } = this;
37
77
  const now = Date.now();
38
78
  let cleared = 0;
39
- for (const [sid, d] of _data) {
79
+ for (const [sid, d] of this.entries()) {
40
80
  if (now < d.expiresAt) {
41
81
  continue;
42
82
  }
43
- _data.delete(sid);
44
- cleared++;
83
+ if (this.destroy(sid)) {
84
+ cleared++;
85
+ }
86
+ }
87
+ if (cleared) {
88
+ this.event.emit("cleanup", this, cleared);
45
89
  }
46
90
  return cleared;
47
91
  }
48
- autoCleanup(interval = 36e5, onCleanup = () => {
49
- }) {
92
+ autoCleanup(interval) {
93
+ const { defaultTTL } = this;
94
+ if (!interval) {
95
+ interval = defaultTTL / 10;
96
+ }
50
97
  const tid = setInterval(() => {
51
- const cleared = this.cleanup();
52
- if (cleared) {
53
- onCleanup(cleared);
54
- }
98
+ this.cleanup();
55
99
  }, interval);
56
100
  return (_) => clearInterval(tid);
57
101
  }
58
102
  };
59
103
 
104
+ // src/socketSession.js
105
+ import { solids } from "@randajan/props";
106
+ import { createQueue } from "@randajan/queue";
107
+ var sidLocks = /* @__PURE__ */ new Map();
108
+ var touchQueues = /* @__PURE__ */ new Map();
109
+ var createSessionCtx = (sessionId, session2, socket) => solids({ session: session2 }, { sessionId, socket });
110
+ var createSessionHash = (session2) => {
111
+ try {
112
+ return JSON.stringify(session2 ?? null);
113
+ } catch {
114
+ return null;
115
+ }
116
+ };
117
+ var isSessionHashChanged = (originalHash, session2) => {
118
+ const nextHash = createSessionHash(session2);
119
+ if (originalHash == null || nextHash == null) {
120
+ return true;
121
+ }
122
+ return originalHash !== nextHash;
123
+ };
124
+ var withLock = async (task, socket, ...args) => {
125
+ const sid = socket.sessionId;
126
+ const previous = sidLocks.get(sid);
127
+ let releaseCurrent;
128
+ const current = new Promise((resolve) => {
129
+ releaseCurrent = resolve;
130
+ });
131
+ sidLocks.set(sid, current);
132
+ if (previous) {
133
+ await previous;
134
+ }
135
+ try {
136
+ return await task(socket, ...args);
137
+ } finally {
138
+ releaseCurrent();
139
+ if (sidLocks.get(sid) === current) {
140
+ sidLocks.delete(sid);
141
+ }
142
+ }
143
+ };
144
+ var getTouchQueue = (sid, store) => {
145
+ const existing = touchQueues.get(sid);
146
+ if (existing) {
147
+ return existing;
148
+ }
149
+ const queue = createQueue(async (touchMaxAge) => {
150
+ try {
151
+ await store.touch(sid, touchMaxAge);
152
+ } catch {
153
+ }
154
+ if (!queue.isPending) {
155
+ touchQueues.delete(sid);
156
+ }
157
+ }, { pass: "last", softMs: 1e3, hardMs: 5e3 });
158
+ touchQueues.set(sid, queue);
159
+ return queue;
160
+ };
161
+ var scheduleTouch = (sid, store, maxAge) => {
162
+ if (typeof store?.touch !== "function") {
163
+ return;
164
+ }
165
+ const queue = getTouchQueue(sid, store);
166
+ queue(maxAge);
167
+ };
168
+ var clearTouchQueue = (sid) => {
169
+ const queue = touchQueues.get(sid);
170
+ if (!queue) {
171
+ return false;
172
+ }
173
+ queue.flush();
174
+ touchQueues.delete(sid);
175
+ return true;
176
+ };
177
+ var runSessionHandler = async (socket, handler, opt = {}) => {
178
+ const sid = socket.sessionId;
179
+ const { store, maxAge } = opt;
180
+ const current = await store.get(sid);
181
+ if (!isObject(current)) {
182
+ throw new Error("Session not found");
183
+ }
184
+ const session2 = current;
185
+ const sessionCtx = createSessionCtx(sid, session2, socket);
186
+ const originalHash = createSessionHash(sessionCtx.session);
187
+ const result = await handler(sessionCtx, socket);
188
+ if (sessionCtx.session == null) {
189
+ clearTouchQueue(sid);
190
+ await store.destroy(sid);
191
+ return result;
192
+ }
193
+ if (!isObject(sessionCtx.session)) {
194
+ throw new TypeError("sessionCtx.session must be an object or null");
195
+ }
196
+ if (isSessionHashChanged(originalHash, sessionCtx.session)) {
197
+ clearTouchQueue(sid);
198
+ await store.set(sid, sessionCtx.session, maxAge);
199
+ } else {
200
+ scheduleTouch(sid, store, maxAge);
201
+ }
202
+ return result;
203
+ };
204
+ var applySessionHandler = async (socket, handler, opt = {}) => {
205
+ if (typeof handler !== "function") {
206
+ throw new TypeError("socket.withSession(handler) requires a function");
207
+ }
208
+ if (!socket.sessionId) {
209
+ throw new Error("Missing session id");
210
+ }
211
+ return withLock(runSessionHandler, socket, handler, opt);
212
+ };
213
+
60
214
  // src/attachSession.js
215
+ var validateStore = (store) => {
216
+ const missing = [];
217
+ if (typeof store?.get !== "function") {
218
+ missing.push("get()");
219
+ }
220
+ if (typeof store?.set !== "function") {
221
+ missing.push("set()");
222
+ }
223
+ if (typeof store?.destroy !== "function") {
224
+ missing.push("destroy()");
225
+ }
226
+ if (typeof store?.touch !== "function") {
227
+ missing.push("touch()");
228
+ }
229
+ if (typeof store?.on !== "function") {
230
+ missing.push("on()");
231
+ }
232
+ if (missing.length) {
233
+ throw new TypeError(`attachSession options.store is missing required API: ${missing.join(", ")}`);
234
+ }
235
+ };
61
236
  var attachSession = (app, io, opt = {}) => {
62
- const signed = "signed" in opt ? !!opt.signed : true;
63
- delete opt.signed;
64
- if (!app.keys) app.keys = Array(6).fill().map(() => generateUid(12));
65
- if (!opt.key) opt.key = generateUid(12);
237
+ opt.signed = "signed" in opt ? !!opt.signed : true;
238
+ if (!app.keys) {
239
+ app.keys = Array(6).fill().map(() => generateUid(12));
240
+ }
241
+ if (!opt.key) {
242
+ opt.key = generateUid(12);
243
+ }
66
244
  if (!opt.maxAge) {
67
245
  opt.maxAge = 864e5;
68
246
  }
69
- if (!opt.store) {
70
- opt.store = new SessionStore(opt.maxAge);
71
- }
72
- const { key, store } = opt;
247
+ const { key, signed, externalKey } = opt;
248
+ const store = opt.store || new SessionStore(opt.maxAge);
249
+ validateStore(store);
250
+ opt.store = wrapStore(store);
251
+ const getSID = externalKey ? (ctx) => externalKey.get(ctx) : (ctx) => ctx.cookies.get(key, { signed });
73
252
  const koaSession = session(opt, app);
74
253
  app.use(koaSession);
75
254
  app.use(async (ctx, next) => {
76
- ctx.session.active = true;
77
- solid2(ctx, "sessionId", ctx.cookies.get(key, { signed }));
255
+ solid2(ctx, "sessionId", getSID(ctx));
78
256
  await next();
79
257
  });
80
258
  io.use(async (socket, next) => {
81
- if (!socket.request.headers.cookie) {
82
- return next(new Error("No cookie"));
83
- }
84
- const ctx = app.createContext(socket.request, socket.response);
259
+ const req = socket.request;
260
+ const res = req.res ?? socket.response ?? new ServerResponse(req);
261
+ const ctx = app.createContext(req, res);
85
262
  await koaSession(ctx, async () => {
86
263
  });
87
- const sid = ctx.cookies.get(key, { signed });
88
- solid2(socket, "sessionId", sid);
89
- virtual(socket, "session", (_) => store.get(sid));
264
+ solid2(socket, "sessionId", getSID(ctx));
265
+ solid2(socket, "withSession", async (handler) => {
266
+ return applySessionHandler(socket, handler, opt);
267
+ }, false);
90
268
  await next();
91
269
  });
92
- return koaSession.store;
270
+ io.on("connection", (socket) => {
271
+ const sid = socket.sessionId;
272
+ if (sid) {
273
+ socket.join(`sessionId:${sid}`);
274
+ }
275
+ });
276
+ store.on("destroy", (_store, sid) => {
277
+ if (!sid) {
278
+ return;
279
+ }
280
+ clearTouchQueue(sid);
281
+ const room = io.in(`sessionId:${sid}`);
282
+ room.emit("session:destroy");
283
+ setTimeout((_) => room.disconnectSockets(true), 200);
284
+ });
285
+ return store;
93
286
  };
94
287
 
95
288
  // src/index.js
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/attachSession.js", "../../src/uid.js", "../../src/SessionStore.js", "../../src/index.js"],
4
- "sourcesContent": ["// attachSession.js \u2013 verze s auto-persist proxy\nimport session from \"koa-session\";\nimport { solid, virtual } from \"@randajan/props\";\nimport { generateUid } from \"./uid\";\nimport { SessionStore } from \"./SessionStore\";\n\n\nexport const attachSession = (app, io, opt = {}) => {\n const signed = \"signed\" in opt ? !!opt.signed : true;\n delete opt.signed;\n\n\n if (!app.keys) app.keys = Array(6).fill().map(() => generateUid(12));\n if (!opt.key) opt.key = generateUid(12);\n if (!opt.maxAge) { opt.maxAge = 86_400_000; }\n if (!opt.store) { opt.store = new SessionStore(opt.maxAge); }\n\n const { key, store } = opt;\n\n const koaSession = session(opt, app);\n app.use(koaSession);\n\n // pro HTTP jen sessionId, nic v\u00EDc nepot\u0159ebujeme\n app.use(async (ctx, next) => {\n ctx.session.active = true;\n solid(ctx, \"sessionId\", ctx.cookies.get(key, { signed }));\n await next();\n });\n\n /* ------------------ WebSocket ------------------------------------- */\n io.use(async (socket, next) => {\n\n if (!socket.request.headers.cookie) { return next(new Error(\"No cookie\")); }\n\n const ctx = app.createContext(socket.request, socket.response);\n await koaSession(ctx, async () => { }); // aktivuj koa-session\n\n const sid = ctx.cookies.get(key, { signed });\n\n solid(socket, \"sessionId\", sid);\n virtual(socket, \"session\", _=>store.get(sid)); // <-- u\u017E se ukl\u00E1d\u00E1 samo\n\n await next();\n });\n\n return koaSession.store; // kdyby ses k n\u011Bmu cht\u011Bl dostat jinde\n};", "import crypto from \"crypto\";\r\n\r\nexport const generateUid = (len = 16) => crypto.randomBytes(len).toString(\"base64url\").slice(0, len);", "import { solid } from \"@randajan/props\";\r\n\r\nexport class SessionStore {\r\n constructor(defaultTTL=86_400_000) {\r\n solid(this, \"_data\", new Map());\r\n solid(this, \"_defaultTTL\", defaultTTL);\r\n }\r\n\r\n get(sid) {\r\n const d = this._data.get(sid);\r\n if (!d) return;\r\n if (Date.now() < d.expiresAt) { return d.session; }\r\n this.destroy(sid);\r\n return {};\r\n }\r\n\r\n set(sid, session, maxAge) {\r\n const { _data, _defaultTTL } = this;\r\n const d = _data.get(sid);\r\n const ttl = maxAge ?? d?.ttl ?? _defaultTTL;\r\n const expiresAt = Date.now() + ttl;\r\n _data.set(sid, { session, expiresAt, ttl });\r\n }\r\n\r\n destroy(sid) { this._data.delete(sid); }\r\n\r\n cleanup() {\r\n const { _data } = this;\r\n\r\n const now = Date.now();\r\n let cleared = 0;\r\n\r\n for (const [sid, d] of _data) {\r\n if (now < d.expiresAt) { continue; }\r\n _data.delete(sid);\r\n cleared++;\r\n }\r\n\r\n return cleared;\r\n }\r\n\r\n autoCleanup(interval=3_600_000, onCleanup=()=>{}) {\r\n const tid = setInterval(() => {\r\n const cleared = this.cleanup();\r\n if (cleared) { onCleanup(cleared); }\r\n }, interval);\r\n return _ => clearInterval(tid);\r\n }\r\n}", "import { attachSession } from \"./attachSession\";\r\nimport { SessionStore } from \"./SessionStore\";\r\nimport { generateUid } from \"./uid\";\r\n\r\n\r\nexport default attachSession;\r\n\r\nexport {\r\n attachSession,\r\n generateUid,\r\n SessionStore\r\n}"],
5
- "mappings": "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",
6
- "names": ["solid", "session", "solid"]
3
+ "sources": ["../../src/attachSession.js", "../../src/tools.js", "../../src/SessionStore.js", "../../src/socketSession.js", "../../src/index.js"],
4
+ "sourcesContent": ["// attachSession.js \u2013 verze s auto-persist proxy\nimport { ServerResponse } from \"http\";\nimport session from \"koa-session\";\nimport { solid } from \"@randajan/props\";\nimport { generateUid } from \"./tools.js\";\nimport { SessionStore, wrapStore } from \"./SessionStore.js\";\nimport { applySessionHandler, clearTouchQueue } from \"./socketSession.js\";\n\n\nconst validateStore = (store) => {\n const missing = [];\n if (typeof store?.get !== \"function\") { missing.push(\"get()\"); }\n if (typeof store?.set !== \"function\") { missing.push(\"set()\"); }\n if (typeof store?.destroy !== \"function\") { missing.push(\"destroy()\"); }\n if (typeof store?.touch !== \"function\") { missing.push(\"touch()\"); }\n if (typeof store?.on!== \"function\") { missing.push(\"on()\"); }\n\n if (missing.length) {\n throw new TypeError(`attachSession options.store is missing required API: ${missing.join(\", \")}`);\n }\n};\n\n\nexport const attachSession = (app, io, opt = {}) => {\n opt.signed = \"signed\" in opt ? !!opt.signed : true;\n\n if (!app.keys) { app.keys = Array(6).fill().map(() => generateUid(12)); }\n if (!opt.key) { opt.key = generateUid(12); }\n if (!opt.maxAge) { opt.maxAge = 86_400_000; }\n\n const { key, signed, externalKey } = opt;\n const store = opt.store || new SessionStore(opt.maxAge);\n validateStore(store);\n opt.store = wrapStore(store);\n\n const getSID = externalKey ? ctx=>externalKey.get(ctx) : ctx=>ctx.cookies.get(key, { signed });\n\n const koaSession = session(opt, app);\n app.use(koaSession);\n\n // pro HTTP jen sessionId, nic v\u00EDc nepot\u0159ebujeme\n app.use(async (ctx, next) => {\n solid(ctx, \"sessionId\", getSID(ctx));\n await next();\n });\n\n /* ------------------ WebSocket ------------------------------------- */\n io.use(async (socket, next) => {\n const req = socket.request;\n const res = req.res ?? socket.response ?? new ServerResponse(req);\n const ctx = app.createContext(req, res);\n\n await koaSession(ctx, async () => {}); //aktivuje session\n \n solid(socket, \"sessionId\", getSID(ctx));\n solid(socket, \"withSession\", async (handler)=>{\n return applySessionHandler(socket, handler, opt);\n }, false);\n\n await next();\n });\n\n io.on(\"connection\", socket=>{\n const sid = socket.sessionId;\n if (sid) { socket.join(`sessionId:${sid}`); }\n });\n\n store.on(\"destroy\", (_store, sid)=>{\n if (!sid) { return; }\n clearTouchQueue(sid);\n const room = io.in(`sessionId:${sid}`);\n room.emit(\"session:destroy\");\n setTimeout(_=>room.disconnectSockets(true), 200);\n });\n\n return store;\n};\n", "import crypto from \"crypto\";\r\n\r\nexport const generateUid = (len = 16) => crypto.randomBytes(len).toString(\"base64url\").slice(0, len);\r\n\r\nexport const isObject = (value) => !!value && typeof value === \"object\";", "import { solid } from \"@randajan/props\";\r\nimport { EventEmitter } from \"events\";\r\n\r\n\r\n\r\nexport const wrapStore = (store)=>{\r\n return {\r\n get:store.get.bind(store),\r\n set:store.set.bind(store),\r\n destroy:store.destroy.bind(store),\r\n touch:store.touch.bind(store)\r\n }\r\n}\r\n\r\nconst formatState = (session, maxAge, prevTTL, defaultTTL)=>{\r\n const ttl = maxAge ?? prevTTL ?? defaultTTL;\r\n const expiresAt = Date.now() + ttl;\r\n return { session, expiresAt, ttl };\r\n}\r\n\r\nexport class SessionStore extends Map {\r\n\r\n constructor(defaultTTL=86_400_000, eventEmitterOpt={}) {\r\n super();\r\n solid(this, \"defaultTTL\", defaultTTL);\r\n solid(this, \"event\", new EventEmitter(eventEmitterOpt));\r\n }\r\n\r\n on(eventName, callback) {\r\n return this.event.on(eventName, callback);\r\n }\r\n\r\n get(sid) {\r\n const d = super.get(sid);\r\n if (!d) { return; }\r\n if (Date.now() < d.expiresAt) { return d.session; }\r\n this.delete(sid);\r\n }\r\n\r\n touch(sid, maxAge) {\r\n const { defaultTTL } = this;\r\n const d = super.get(sid);\r\n if (!d) { return false; }\r\n super.set(sid, formatState(d.session, maxAge, d.ttl, defaultTTL));\r\n this.event.emit(\"touch\", this, sid);\r\n return true;\r\n }\r\n\r\n set(sid, session, maxAge) {\r\n const { defaultTTL } = this;\r\n const d = super.get(sid);\r\n if (session == null) { return !d || this.destroy(sid); }\r\n super.set(sid, formatState(session, maxAge, d?.ttl, defaultTTL));\r\n this.event.emit(\"set\", this, sid, !d);\r\n return true;\r\n }\r\n\r\n delete(sid) {\r\n return this.destroy(sid);\r\n }\r\n\r\n destroy(sid) {\r\n if (this.has(sid)) {\r\n super.delete(sid);\r\n this.event.emit(\"destroy\", this, sid);\r\n }\r\n return true;\r\n }\r\n\r\n cleanup() {\r\n const now = Date.now();\r\n let cleared = 0;\r\n\r\n for (const [sid, d] of this.entries()) {\r\n if (now < d.expiresAt) { continue; }\r\n if (this.destroy(sid)) { cleared++; }\r\n }\r\n\r\n if (cleared) { this.event.emit(\"cleanup\", this, cleared); }\r\n\r\n return cleared;\r\n }\r\n\r\n autoCleanup(interval) {\r\n const { defaultTTL } = this;\r\n if (!interval) { interval = defaultTTL/10; }\r\n\r\n const tid = setInterval(() => {\r\n this.cleanup();\r\n }, interval);\r\n return _ => clearInterval(tid);\r\n }\r\n}\r\n", "import { solids } from \"@randajan/props\";\nimport { createQueue } from \"@randajan/queue\";\nimport { isObject } from \"./tools\";\n\n\nconst sidLocks = new Map();\nconst touchQueues = new Map();\n\nconst createSessionCtx = (sessionId, session, socket) =>solids({ session }, { sessionId, socket });\n\nconst createSessionHash = (session) => {\n try { return JSON.stringify(session ?? null); }\n catch { return null; }\n};\n\nconst isSessionHashChanged = (originalHash, session) => {\n const nextHash = createSessionHash(session);\n if (originalHash == null || nextHash == null) { return true; }\n return originalHash !== nextHash;\n};\n\nconst withLock = async (task, socket, ...args) => {\n const sid = socket.sessionId;\n const previous = sidLocks.get(sid);\n let releaseCurrent;\n const current = new Promise((resolve) => { releaseCurrent = resolve; });\n sidLocks.set(sid, current);\n\n if (previous) { await previous; }\n\n try {\n return await task(socket, ...args);\n } finally {\n releaseCurrent();\n if (sidLocks.get(sid) === current) { sidLocks.delete(sid); }\n }\n};\n\nconst getTouchQueue = (sid, store) => {\n const existing = touchQueues.get(sid);\n if (existing) { return existing; }\n\n const queue = createQueue(async (touchMaxAge) => {\n try { await store.touch(sid, touchMaxAge); } catch {}\n if (!queue.isPending) { touchQueues.delete(sid); }\n }, { pass: \"last\", softMs:1_000, hardMs:5_000 });\n\n touchQueues.set(sid, queue);\n return queue;\n};\n\nconst scheduleTouch = (sid, store, maxAge) => {\n if (typeof store?.touch !== \"function\") { return; }\n const queue = getTouchQueue(sid, store);\n queue(maxAge);\n};\n\nexport const clearTouchQueue = (sid) => {\n const queue = touchQueues.get(sid);\n if (!queue) { return false; }\n queue.flush();\n touchQueues.delete(sid);\n return true;\n};\n\nconst runSessionHandler = async (socket, handler, opt={}) => {\n const sid = socket.sessionId;\n const { store, maxAge } = opt;\n const current = await store.get(sid);\n\n if (!isObject(current)) { throw new Error(\"Session not found\"); }\n const session = current;\n const sessionCtx = createSessionCtx(sid, session, socket);\n\n const originalHash = createSessionHash(sessionCtx.session);\n const result = await handler(sessionCtx, socket);\n\n if (sessionCtx.session == null) {\n clearTouchQueue(sid);\n await store.destroy(sid);\n return result;\n }\n\n if (!isObject(sessionCtx.session)) {\n throw new TypeError(\"sessionCtx.session must be an object or null\");\n }\n\n if (isSessionHashChanged(originalHash, sessionCtx.session)) {\n clearTouchQueue(sid);\n await store.set(sid, sessionCtx.session, maxAge);\n } else {\n scheduleTouch(sid, store, maxAge);\n }\n\n return result;\n};\n\nexport const applySessionHandler = async (socket, handler, opt={}) => {\n\n if (typeof handler !== \"function\") {\n throw new TypeError(\"socket.withSession(handler) requires a function\");\n }\n if (!socket.sessionId) {\n throw new Error(\"Missing session id\");\n }\n\n return withLock(runSessionHandler, socket, handler, opt);\n};\n", "import { attachSession } from \"./attachSession.js\";\nimport { SessionStore } from \"./SessionStore.js\";\nimport { generateUid } from \"./tools.js\";\n\n\nexport default attachSession;\n\nexport {\n attachSession,\n generateUid,\n SessionStore\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["solid", "session", "session", "solid"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@randajan/koa-io-session",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Simple bridge between koa-session and socket.io. Shares a unified session across HTTP and WebSocket using a common session store.",
5
5
  "repository": "randajan/koa-io-session",
6
6
  "type": "module",
@@ -14,7 +14,10 @@
14
14
  },
15
15
  "license": "MIT",
16
16
  "devDependencies": {
17
- "@randajan/simple-lib": "^3.2.0"
17
+ "@randajan/simple-lib": "^3.2.0",
18
+ "koa": "^3.1.2",
19
+ "socket.io": "^4.8.3",
20
+ "socket.io-client": "^4.8.3"
18
21
  },
19
22
  "files": [
20
23
  "dist",
@@ -44,6 +47,7 @@
44
47
  },
45
48
  "dependencies": {
46
49
  "@randajan/props": "^0.1.4",
50
+ "@randajan/queue": "^1.0.0",
47
51
  "crypto": "^1.0.1",
48
52
  "koa-session": "^7.0.2"
49
53
  }