@gaonjs/web 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +65 -0
- package/dist/app.js +123 -0
- package/dist/auth.d.ts +38 -0
- package/dist/auth.js +78 -0
- package/dist/controller.d.ts +93 -4
- package/dist/controller.js +51 -13
- package/dist/dispatch.d.ts +30 -0
- package/dist/dispatch.js +121 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +25 -7
- package/dist/inertia.d.ts +24 -0
- package/dist/inertia.js +74 -0
- package/dist/jwt.d.ts +7 -0
- package/dist/jwt.js +29 -0
- package/dist/password.d.ts +4 -0
- package/dist/password.js +17 -0
- package/dist/redisStore.d.ts +10 -0
- package/dist/redisStore.js +37 -0
- package/dist/routes.d.ts +13 -8
- package/dist/routes.js +55 -22
- package/dist/serialize.d.ts +5 -0
- package/dist/serialize.js +46 -0
- package/dist/session.d.ts +27 -0
- package/dist/session.js +76 -0
- package/dist/websocket.d.ts +28 -0
- package/dist/websocket.js +103 -0
- package/package.json +13 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @gaonjs/web · WebSocket 전송 계층 (§7 실시간 · M6)
|
|
2
|
+
//
|
|
3
|
+
// 채널의 전송을 @fastify/websocket 으로 붙인다. 채널 정의(@gaonjs/async 의
|
|
4
|
+
// channel())는 전송을 모르고, 여기서 raw WebSocket 을 SocketAdapter 로 감싸
|
|
5
|
+
// ChannelRuntime 에 넘긴다. 라우트는 앱 캡슐화 스코프 안에 등록되므로
|
|
6
|
+
// 세션(M5)이 그대로 살아 있어 연결을 인증할 수 있다.
|
|
7
|
+
//
|
|
8
|
+
// 경로 관례: `<앱 프리픽스>/gaon/ws/:channel`. 채널명은 apps/*/channels/
|
|
9
|
+
// 파일명이다(파일=등록). 알 수 없는 채널은 4404 로 닫는다.
|
|
10
|
+
import websocketPlugin from '@fastify/websocket';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
/**
|
|
13
|
+
* @fastify/websocket 을 등록한다. 전역 upgrade 훅을 걸므로 서버당 **한 번만**
|
|
14
|
+
* (루트에서) 호출해야 한다. 라우트(registerChannels)는 이후 어느 스코프에서든
|
|
15
|
+
* 정의할 수 있다.
|
|
16
|
+
*/
|
|
17
|
+
export async function registerWebSocketSupport(app) {
|
|
18
|
+
await app.register(websocketPlugin);
|
|
19
|
+
}
|
|
20
|
+
/** 세션 userId 로 멤버를 정한다. 비로그인은 익명 연결 id. */
|
|
21
|
+
async function defaultResolveMember(request) {
|
|
22
|
+
const sess = request.session;
|
|
23
|
+
const uid = sess?.userId;
|
|
24
|
+
if (uid != null)
|
|
25
|
+
return { member: `user:${String(uid)}`, user: { id: uid } };
|
|
26
|
+
return { member: `conn:${randomUUID()}`, user: null };
|
|
27
|
+
}
|
|
28
|
+
function queryOf(request) {
|
|
29
|
+
const q = request.query;
|
|
30
|
+
const out = {};
|
|
31
|
+
if (q)
|
|
32
|
+
for (const [k, v] of Object.entries(q))
|
|
33
|
+
if (typeof v === 'string')
|
|
34
|
+
out[k] = v;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 앱 스코프에 채널 ws 라우트를 등록한다. 연결마다 멤버를 해석하고 런타임에
|
|
39
|
+
* join 을 위임하며, 소켓 메시지를 채널 정의의 onMessage 로 흘린다.
|
|
40
|
+
*/
|
|
41
|
+
export function registerChannels(scope, opts) {
|
|
42
|
+
// @fastify/websocket 은 registerWebSocketSupport 로 루트에서 이미 등록됐다고
|
|
43
|
+
// 가정한다(전역 upgrade 훅 중복 방지). 여기서는 채널 라우트만 정의한다.
|
|
44
|
+
const resolve = opts.resolveMember ?? defaultResolveMember;
|
|
45
|
+
const routePath = opts.path ?? '/gaon/ws/:channel';
|
|
46
|
+
scope.get(routePath, { websocket: true }, (socket, request) => {
|
|
47
|
+
const name = request.params.channel ?? '';
|
|
48
|
+
const def = opts.channels[name];
|
|
49
|
+
if (!def) {
|
|
50
|
+
socket.close(4404, 'unknown channel');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const adapter = {
|
|
54
|
+
send: (text) => {
|
|
55
|
+
if (socket.readyState === socket.OPEN)
|
|
56
|
+
socket.send(text);
|
|
57
|
+
},
|
|
58
|
+
close: (code, reason) => socket.close(code, reason),
|
|
59
|
+
};
|
|
60
|
+
// join 은 비동기(허브 왕복). 그 사이 도착한 메시지는 버퍼링했다가 재생한다.
|
|
61
|
+
const pending = [];
|
|
62
|
+
let conn = null;
|
|
63
|
+
let ready = false;
|
|
64
|
+
let closed = false;
|
|
65
|
+
socket.on('message', (raw) => {
|
|
66
|
+
const text = typeof raw === 'string' ? raw : String(raw);
|
|
67
|
+
if (ready && conn)
|
|
68
|
+
void conn.receive(text);
|
|
69
|
+
else
|
|
70
|
+
pending.push(text);
|
|
71
|
+
});
|
|
72
|
+
socket.on('close', () => {
|
|
73
|
+
closed = true;
|
|
74
|
+
if (conn)
|
|
75
|
+
void conn.leave();
|
|
76
|
+
});
|
|
77
|
+
void (async () => {
|
|
78
|
+
const { member, user } = await resolve(request);
|
|
79
|
+
const c = await opts.runtime.join({
|
|
80
|
+
channel: name,
|
|
81
|
+
def,
|
|
82
|
+
member,
|
|
83
|
+
user,
|
|
84
|
+
query: queryOf(request),
|
|
85
|
+
socket: adapter,
|
|
86
|
+
});
|
|
87
|
+
if (!c)
|
|
88
|
+
return; // 인가 거부 — 런타임이 소켓을 닫았다.
|
|
89
|
+
conn = c;
|
|
90
|
+
if (closed) {
|
|
91
|
+
void c.leave(); // join 도중 이미 끊겼으면 즉시 정리.
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
ready = true;
|
|
95
|
+
for (const t of pending)
|
|
96
|
+
void c.receive(t);
|
|
97
|
+
pending.length = 0;
|
|
98
|
+
})().catch((err) => {
|
|
99
|
+
adapter.send(JSON.stringify({ t: 'error', message: err instanceof Error ? err.message : String(err) }));
|
|
100
|
+
socket.close(1011, 'join failed');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/web",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Gaon 웹 레이어: Fastify 통합·파일 기반 라우팅·보안 기본값 (구현 예정)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,18 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@
|
|
27
|
+
"@fastify/cookie": "^11.1.2",
|
|
28
|
+
"@fastify/cors": "^11.3.0",
|
|
29
|
+
"@fastify/csrf-protection": "^8.0.0",
|
|
30
|
+
"@fastify/rate-limit": "^11.1.0",
|
|
31
|
+
"@fastify/session": "^11.1.2",
|
|
32
|
+
"@fastify/websocket": "^11.0.2",
|
|
33
|
+
"bcryptjs": "^3.0.3",
|
|
34
|
+
"fastify": "^5.10.0",
|
|
35
|
+
"ioredis": "^5.11.1",
|
|
36
|
+
"jose": "^6.2.4",
|
|
37
|
+
"@gaonjs/core": "0.1.3",
|
|
38
|
+
"@gaonjs/async": "0.2.0"
|
|
28
39
|
},
|
|
29
40
|
"scripts": {
|
|
30
41
|
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json"
|