@randajan/koa-io-session 0.0.10 → 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 +203 -22
- package/dist/cjs/index.cjs +237 -59
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/esm/index.mjs +236 -58
- package/dist/esm/index.mjs.map +4 -4
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@randajan/koa-io-session) [](https://standardjs.com)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
|
27
|
-
const io = new 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
|
|
49
|
+
key: "koa.io.sid",
|
|
31
50
|
signed: true,
|
|
32
|
-
maxAge:
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
133
|
+
```js
|
|
134
|
+
await socket.withSession(async (sessionCtx) => {
|
|
135
|
+
sessionCtx.session = null;
|
|
38
136
|
});
|
|
39
137
|
```
|
|
40
138
|
|
|
41
139
|
---
|
|
42
140
|
|
|
43
|
-
##
|
|
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
|
-
|
|
46
|
-
- `
|
|
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
|
-
##
|
|
210
|
+
## Options
|
|
211
|
+
|
|
212
|
+
`attachSession(app, io, opt)` forwards session options to `koa-session`.
|
|
51
213
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
-
|
|
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
|
|
245
|
+
MIT (c) [randajan](https://github.com/randajan)
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -37,112 +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
|
|
42
|
+
var import_props3 = require("@randajan/props");
|
|
42
43
|
|
|
43
|
-
// src/
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
56
|
-
if (!d)
|
|
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.
|
|
61
|
-
|
|
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 {
|
|
65
|
-
const d =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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.
|
|
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
|
|
117
|
+
for (const [sid, d] of this.entries()) {
|
|
78
118
|
if (now < d.expiresAt) {
|
|
79
119
|
continue;
|
|
80
120
|
}
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
87
|
-
|
|
130
|
+
autoCleanup(interval) {
|
|
131
|
+
const { defaultTTL } = this;
|
|
132
|
+
if (!interval) {
|
|
133
|
+
interval = defaultTTL / 10;
|
|
134
|
+
}
|
|
88
135
|
const tid = setInterval(() => {
|
|
89
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
set(target, prop, value, receiver) {
|
|
130
|
-
const ok = Reflect.set(target, prop, value, receiver);
|
|
131
|
-
persist();
|
|
132
|
-
return ok;
|
|
133
|
-
},
|
|
134
|
-
deleteProperty(target, prop) {
|
|
135
|
-
const ok = Reflect.deleteProperty(target, prop);
|
|
136
|
-
persist();
|
|
137
|
-
return ok;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
socket.once("disconnect", persist);
|
|
141
|
-
(0, import_props2.solid)(socket, "sessionId", sid);
|
|
142
|
-
(0, import_props2.solid)(socket, "session", liveSession);
|
|
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);
|
|
143
306
|
await next();
|
|
144
307
|
});
|
|
145
|
-
|
|
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;
|
|
146
324
|
};
|
|
147
325
|
|
|
148
326
|
// src/index.js
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.js", "../../src/attachSession.js", "../../src/
|
|
4
|
-
"sourcesContent": ["import { attachSession } from \"./attachSession\";\
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,yBAAoB;AACpB,IAAAA,gBAAsB;;;
|
|
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
|
}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,110 +1,288 @@
|
|
|
1
1
|
// src/attachSession.js
|
|
2
|
+
import { ServerResponse } from "http";
|
|
2
3
|
import session from "koa-session";
|
|
3
4
|
import { solid as solid2 } from "@randajan/props";
|
|
4
5
|
|
|
5
|
-
// src/
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 =
|
|
18
|
-
if (!d)
|
|
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.
|
|
23
|
-
|
|
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 {
|
|
27
|
-
const d =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
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
|
|
79
|
+
for (const [sid, d] of this.entries()) {
|
|
40
80
|
if (now < d.expiresAt) {
|
|
41
81
|
continue;
|
|
42
82
|
}
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
49
|
-
|
|
92
|
+
autoCleanup(interval) {
|
|
93
|
+
const { defaultTTL } = this;
|
|
94
|
+
if (!interval) {
|
|
95
|
+
interval = defaultTTL / 10;
|
|
96
|
+
}
|
|
50
97
|
const tid = setInterval(() => {
|
|
51
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
set(target, prop, value, receiver) {
|
|
92
|
-
const ok = Reflect.set(target, prop, value, receiver);
|
|
93
|
-
persist();
|
|
94
|
-
return ok;
|
|
95
|
-
},
|
|
96
|
-
deleteProperty(target, prop) {
|
|
97
|
-
const ok = Reflect.deleteProperty(target, prop);
|
|
98
|
-
persist();
|
|
99
|
-
return ok;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
socket.once("disconnect", persist);
|
|
103
|
-
solid2(socket, "sessionId", sid);
|
|
104
|
-
solid2(socket, "session", liveSession);
|
|
264
|
+
solid2(socket, "sessionId", getSID(ctx));
|
|
265
|
+
solid2(socket, "withSession", async (handler) => {
|
|
266
|
+
return applySessionHandler(socket, handler, opt);
|
|
267
|
+
}, false);
|
|
105
268
|
await next();
|
|
106
269
|
});
|
|
107
|
-
|
|
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;
|
|
108
286
|
};
|
|
109
287
|
|
|
110
288
|
// src/index.js
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/attachSession.js", "../../src/
|
|
4
|
-
"sourcesContent": ["// attachSession.js \u2013 verze s auto-persist proxy\nimport session from \"koa-session\";\nimport { solid } from \"@randajan/props\";\nimport { generateUid } from \"./
|
|
5
|
-
"mappings": ";AACA,OAAO,aAAa;AACpB,SAAS,SAAAA,cAAa;;;
|
|
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": ";AACA,SAAS,sBAAsB;AAC/B,OAAO,aAAa;AACpB,SAAS,SAAAA,cAAa;;;ACHtB,OAAO,YAAY;AAEZ,IAAM,cAAc,CAAC,MAAM,OAAO,OAAO,YAAY,GAAG,EAAE,SAAS,WAAW,EAAE,MAAM,GAAG,GAAG;AAE5F,IAAM,WAAW,CAAC,UAAU,CAAC,CAAC,SAAS,OAAO,UAAU;;;ACJ/D,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAItB,IAAM,YAAY,CAAC,UAAQ;AAC9B,SAAO;AAAA,IACH,KAAI,MAAM,IAAI,KAAK,KAAK;AAAA,IACxB,KAAI,MAAM,IAAI,KAAK,KAAK;AAAA,IACxB,SAAQ,MAAM,QAAQ,KAAK,KAAK;AAAA,IAChC,OAAM,MAAM,MAAM,KAAK,KAAK;AAAA,EAChC;AACJ;AAEA,IAAM,cAAc,CAACC,UAAS,QAAQ,SAAS,eAAa;AACxD,QAAM,MAAM,UAAU,WAAW;AACjC,QAAM,YAAY,KAAK,IAAI,IAAI;AAC/B,SAAO,EAAE,SAAAA,UAAS,WAAW,IAAI;AACrC;AAEO,IAAM,eAAN,cAA2B,IAAI;AAAA,EAElC,YAAY,aAAW,OAAY,kBAAgB,CAAC,GAAG;AACnD,UAAM;AACN,UAAM,MAAM,cAAc,UAAU;AACpC,UAAM,MAAM,SAAS,IAAI,aAAa,eAAe,CAAC;AAAA,EAC1D;AAAA,EAEA,GAAG,WAAW,UAAU;AACpB,WAAO,KAAK,MAAM,GAAG,WAAW,QAAQ;AAAA,EAC5C;AAAA,EAEA,IAAI,KAAK;AACL,UAAM,IAAI,MAAM,IAAI,GAAG;AACvB,QAAI,CAAC,GAAG;AAAE;AAAA,IAAQ;AAClB,QAAI,KAAK,IAAI,IAAI,EAAE,WAAW;AAAE,aAAO,EAAE;AAAA,IAAS;AAClD,SAAK,OAAO,GAAG;AAAA,EACnB;AAAA,EAEA,MAAM,KAAK,QAAQ;AACf,UAAM,EAAE,WAAW,IAAI;AACvB,UAAM,IAAI,MAAM,IAAI,GAAG;AACvB,QAAI,CAAC,GAAG;AAAE,aAAO;AAAA,IAAO;AACxB,UAAM,IAAI,KAAK,YAAY,EAAE,SAAS,QAAQ,EAAE,KAAK,UAAU,CAAC;AAChE,SAAK,MAAM,KAAK,SAAS,MAAM,GAAG;AAClC,WAAO;AAAA,EACX;AAAA,EAEA,IAAI,KAAKA,UAAS,QAAQ;AACtB,UAAM,EAAE,WAAW,IAAI;AACvB,UAAM,IAAI,MAAM,IAAI,GAAG;AACvB,QAAIA,YAAW,MAAM;AAAE,aAAO,CAAC,KAAK,KAAK,QAAQ,GAAG;AAAA,IAAG;AACvD,UAAM,IAAI,KAAK,YAAYA,UAAS,QAAQ,GAAG,KAAK,UAAU,CAAC;AAC/D,SAAK,MAAM,KAAK,OAAO,MAAM,KAAK,CAAC,CAAC;AACpC,WAAO;AAAA,EACX;AAAA,EAEA,OAAO,KAAK;AACR,WAAO,KAAK,QAAQ,GAAG;AAAA,EAC3B;AAAA,EAEA,QAAQ,KAAK;AACT,QAAI,KAAK,IAAI,GAAG,GAAG;AACf,YAAM,OAAO,GAAG;AAChB,WAAK,MAAM,KAAK,WAAW,MAAM,GAAG;AAAA,IACxC;AACA,WAAO;AAAA,EACX;AAAA,EAEA,UAAU;AACN,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,UAAU;AAEd,eAAW,CAAC,KAAK,CAAC,KAAK,KAAK,QAAQ,GAAG;AACnC,UAAI,MAAM,EAAE,WAAW;AAAE;AAAA,MAAU;AACnC,UAAI,KAAK,QAAQ,GAAG,GAAG;AAAE;AAAA,MAAW;AAAA,IACxC;AAEA,QAAI,SAAS;AAAE,WAAK,MAAM,KAAK,WAAW,MAAM,OAAO;AAAA,IAAG;AAE1D,WAAO;AAAA,EACX;AAAA,EAEA,YAAY,UAAU;AAClB,UAAM,EAAE,WAAW,IAAI;AACvB,QAAI,CAAC,UAAU;AAAE,iBAAW,aAAW;AAAA,IAAI;AAE3C,UAAM,MAAM,YAAY,MAAM;AAC1B,WAAK,QAAQ;AAAA,IACjB,GAAG,QAAQ;AACX,WAAO,OAAK,cAAc,GAAG;AAAA,EACjC;AACJ;;;AC5FA,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAI5B,IAAM,WAAW,oBAAI,IAAI;AACzB,IAAM,cAAc,oBAAI,IAAI;AAE5B,IAAM,mBAAmB,CAAC,WAAWC,UAAS,WAAU,OAAO,EAAE,SAAAA,SAAQ,GAAG,EAAE,WAAW,OAAO,CAAC;AAEjG,IAAM,oBAAoB,CAACA,aAAY;AACnC,MAAI;AAAE,WAAO,KAAK,UAAUA,YAAW,IAAI;AAAA,EAAG,QACxC;AAAE,WAAO;AAAA,EAAM;AACzB;AAEA,IAAM,uBAAuB,CAAC,cAAcA,aAAY;AACpD,QAAM,WAAW,kBAAkBA,QAAO;AAC1C,MAAI,gBAAgB,QAAQ,YAAY,MAAM;AAAE,WAAO;AAAA,EAAM;AAC7D,SAAO,iBAAiB;AAC5B;AAEA,IAAM,WAAW,OAAO,MAAM,WAAW,SAAS;AAC9C,QAAM,MAAM,OAAO;AACnB,QAAM,WAAW,SAAS,IAAI,GAAG;AACjC,MAAI;AACJ,QAAM,UAAU,IAAI,QAAQ,CAAC,YAAY;AAAE,qBAAiB;AAAA,EAAS,CAAC;AACtE,WAAS,IAAI,KAAK,OAAO;AAEzB,MAAI,UAAU;AAAE,UAAM;AAAA,EAAU;AAEhC,MAAI;AACA,WAAO,MAAM,KAAK,QAAQ,GAAG,IAAI;AAAA,EACrC,UAAE;AACE,mBAAe;AACf,QAAI,SAAS,IAAI,GAAG,MAAM,SAAS;AAAE,eAAS,OAAO,GAAG;AAAA,IAAG;AAAA,EAC/D;AACJ;AAEA,IAAM,gBAAgB,CAAC,KAAK,UAAU;AAClC,QAAM,WAAW,YAAY,IAAI,GAAG;AACpC,MAAI,UAAU;AAAE,WAAO;AAAA,EAAU;AAEjC,QAAM,QAAQ,YAAY,OAAO,gBAAgB;AAC7C,QAAI;AAAE,YAAM,MAAM,MAAM,KAAK,WAAW;AAAA,IAAG,QAAQ;AAAA,IAAC;AACpD,QAAI,CAAC,MAAM,WAAW;AAAE,kBAAY,OAAO,GAAG;AAAA,IAAG;AAAA,EACrD,GAAG,EAAE,MAAM,QAAQ,QAAO,KAAO,QAAO,IAAM,CAAC;AAE/C,cAAY,IAAI,KAAK,KAAK;AAC1B,SAAO;AACX;AAEA,IAAM,gBAAgB,CAAC,KAAK,OAAO,WAAW;AAC1C,MAAI,OAAO,OAAO,UAAU,YAAY;AAAE;AAAA,EAAQ;AAClD,QAAM,QAAQ,cAAc,KAAK,KAAK;AACtC,QAAM,MAAM;AAChB;AAEO,IAAM,kBAAkB,CAAC,QAAQ;AACpC,QAAM,QAAQ,YAAY,IAAI,GAAG;AACjC,MAAI,CAAC,OAAO;AAAE,WAAO;AAAA,EAAO;AAC5B,QAAM,MAAM;AACZ,cAAY,OAAO,GAAG;AACtB,SAAO;AACX;AAEA,IAAM,oBAAoB,OAAO,QAAQ,SAAS,MAAI,CAAC,MAAM;AACzD,QAAM,MAAM,OAAO;AACnB,QAAM,EAAE,OAAO,OAAO,IAAI;AAC1B,QAAM,UAAU,MAAM,MAAM,IAAI,GAAG;AAEnC,MAAI,CAAC,SAAS,OAAO,GAAG;AAAE,UAAM,IAAI,MAAM,mBAAmB;AAAA,EAAG;AAChE,QAAMA,WAAU;AAChB,QAAM,aAAa,iBAAiB,KAAKA,UAAS,MAAM;AAExD,QAAM,eAAe,kBAAkB,WAAW,OAAO;AACzD,QAAM,SAAS,MAAM,QAAQ,YAAY,MAAM;AAE/C,MAAI,WAAW,WAAW,MAAM;AAC5B,oBAAgB,GAAG;AACnB,UAAM,MAAM,QAAQ,GAAG;AACvB,WAAO;AAAA,EACX;AAEA,MAAI,CAAC,SAAS,WAAW,OAAO,GAAG;AAC/B,UAAM,IAAI,UAAU,8CAA8C;AAAA,EACtE;AAEA,MAAI,qBAAqB,cAAc,WAAW,OAAO,GAAG;AACxD,oBAAgB,GAAG;AACnB,UAAM,MAAM,IAAI,KAAK,WAAW,SAAS,MAAM;AAAA,EACnD,OAAO;AACH,kBAAc,KAAK,OAAO,MAAM;AAAA,EACpC;AAEA,SAAO;AACX;AAEO,IAAM,sBAAsB,OAAO,QAAQ,SAAS,MAAI,CAAC,MAAM;AAElE,MAAI,OAAO,YAAY,YAAY;AAC/B,UAAM,IAAI,UAAU,iDAAiD;AAAA,EACzE;AACA,MAAI,CAAC,OAAO,WAAW;AACnB,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACxC;AAEA,SAAO,SAAS,mBAAmB,QAAQ,SAAS,GAAG;AAC3D;;;AHlGA,IAAM,gBAAgB,CAAC,UAAU;AAC7B,QAAM,UAAU,CAAC;AACjB,MAAI,OAAO,OAAO,QAAQ,YAAY;AAAE,YAAQ,KAAK,OAAO;AAAA,EAAG;AAC/D,MAAI,OAAO,OAAO,QAAQ,YAAY;AAAE,YAAQ,KAAK,OAAO;AAAA,EAAG;AAC/D,MAAI,OAAO,OAAO,YAAY,YAAY;AAAE,YAAQ,KAAK,WAAW;AAAA,EAAG;AACvE,MAAI,OAAO,OAAO,UAAU,YAAY;AAAE,YAAQ,KAAK,SAAS;AAAA,EAAG;AACnE,MAAI,OAAO,OAAO,OAAM,YAAY;AAAE,YAAQ,KAAK,MAAM;AAAA,EAAG;AAE5D,MAAI,QAAQ,QAAQ;AAChB,UAAM,IAAI,UAAU,wDAAwD,QAAQ,KAAK,IAAI,CAAC,EAAE;AAAA,EACpG;AACJ;AAGO,IAAM,gBAAgB,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM;AAChD,MAAI,SAAS,YAAY,MAAM,CAAC,CAAC,IAAI,SAAS;AAE9C,MAAI,CAAC,IAAI,MAAM;AAAE,QAAI,OAAO,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,MAAM,YAAY,EAAE,CAAC;AAAA,EAAG;AACxE,MAAI,CAAC,IAAI,KAAK;AAAE,QAAI,MAAM,YAAY,EAAE;AAAA,EAAG;AAC3C,MAAI,CAAC,IAAI,QAAQ;AAAE,QAAI,SAAS;AAAA,EAAY;AAE5C,QAAM,EAAE,KAAK,QAAQ,YAAY,IAAI;AACrC,QAAM,QAAQ,IAAI,SAAS,IAAI,aAAa,IAAI,MAAM;AACtD,gBAAc,KAAK;AACnB,MAAI,QAAQ,UAAU,KAAK;AAE3B,QAAM,SAAS,cAAc,SAAK,YAAY,IAAI,GAAG,IAAI,SAAK,IAAI,QAAQ,IAAI,KAAK,EAAE,OAAO,CAAC;AAE7F,QAAM,aAAa,QAAQ,KAAK,GAAG;AACnC,MAAI,IAAI,UAAU;AAGlB,MAAI,IAAI,OAAO,KAAK,SAAS;AACzB,IAAAC,OAAM,KAAK,aAAa,OAAO,GAAG,CAAC;AACnC,UAAM,KAAK;AAAA,EACf,CAAC;AAGD,KAAG,IAAI,OAAO,QAAQ,SAAS;AAC3B,UAAM,MAAM,OAAO;AACnB,UAAM,MAAM,IAAI,OAAO,OAAO,YAAY,IAAI,eAAe,GAAG;AAChE,UAAM,MAAM,IAAI,cAAc,KAAK,GAAG;AAEtC,UAAM,WAAW,KAAK,YAAY;AAAA,IAAC,CAAC;AAEpC,IAAAA,OAAM,QAAQ,aAAa,OAAO,GAAG,CAAC;AACtC,IAAAA,OAAM,QAAQ,eAAe,OAAO,YAAU;AAC1C,aAAO,oBAAoB,QAAQ,SAAS,GAAG;AAAA,IACnD,GAAG,KAAK;AAER,UAAM,KAAK;AAAA,EACf,CAAC;AAED,KAAG,GAAG,cAAc,YAAQ;AACxB,UAAM,MAAM,OAAO;AACnB,QAAI,KAAK;AAAE,aAAO,KAAK,aAAa,GAAG,EAAE;AAAA,IAAG;AAAA,EAChD,CAAC;AAED,QAAM,GAAG,WAAW,CAAC,QAAQ,QAAM;AAC/B,QAAI,CAAC,KAAK;AAAE;AAAA,IAAQ;AACpB,oBAAgB,GAAG;AACnB,UAAM,OAAO,GAAG,GAAG,aAAa,GAAG,EAAE;AACrC,SAAK,KAAK,iBAAiB;AAC3B,eAAW,OAAG,KAAK,kBAAkB,IAAI,GAAG,GAAG;AAAA,EACnD,CAAC;AAED,SAAO;AACX;;;AIvEA,IAAO,gBAAQ;",
|
|
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.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
|
}
|