mega-framework 0.1.6 → 0.1.8
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 +9 -0
- package/bin/mega-ws-hub.js +2 -2
- package/package.json +33 -9
- package/sample/crud/.env +10 -1
- package/sample/crud/.env.example +10 -1
- package/sample/crud/.mega/journal/history/20260612092543-create-users.json +261 -0
- package/sample/crud/.mega/journal/snapshot.json +261 -0
- package/sample/crud/apps/main/controllers/auth-controller.js +22 -14
- package/sample/crud/apps/main/controllers/web-controller.js +7 -5
- package/sample/crud/apps/main/locales/server/en.json +12 -1
- package/sample/crud/apps/main/locales/server/ko.json +12 -1
- package/sample/crud/apps/main/migrations/20260606000001-create-users.js +91 -13
- package/sample/crud/apps/main/migrations/20260606000002-create-boards.js +165 -0
- package/sample/crud/apps/main/migrations/20260606000003-create-logs.js +107 -0
- package/sample/crud/apps/main/models/log-partition-model.js +105 -0
- package/sample/crud/apps/main/models/note-model.js +79 -0
- package/sample/crud/apps/main/models/user-level-model.js +24 -0
- package/sample/crud/apps/main/models/user-model.js +146 -0
- package/sample/crud/apps/main/models/user-type-model.js +21 -0
- package/sample/crud/apps/main/models/wallet-model.js +24 -0
- package/sample/crud/apps/main/routes/users.js +55 -10
- package/sample/crud/apps/main/schedules/log-partition-schedule.js +33 -0
- package/sample/crud/apps/main/services/auth-service.js +39 -24
- package/sample/crud/apps/main/services/log-partition-service.js +101 -0
- package/sample/crud/apps/main/services/note-service.js +6 -6
- package/sample/crud/apps/main/services/redis-demo-service.js +3 -3
- package/sample/crud/apps/main/services/user-service.js +62 -21
- package/sample/crud/apps/main/views/auth/login.ejs +6 -6
- package/sample/crud/apps/main/views/auth/register.ejs +46 -5
- package/sample/crud/apps/main/views/users/edit.ejs +42 -5
- package/sample/crud/apps/main/views/users/list.ejs +6 -2
- package/sample/crud/apps/main/views/users/new.ejs +56 -4
- package/sample/crud/docs/log_partition_design.mm.md +23 -0
- package/sample/crud/mega.config.js +10 -2
- package/sample/crud/package.json +3 -3
- package/sample/crud/scripts/start-ws-hub.sh +20 -6
- package/sample/simple/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/sample/simple/package.json +2 -2
- package/src/adapters/adapter-manager.js +2 -1
- package/src/adapters/adapter-options.js +44 -3
- package/src/adapters/file-adapter.js +9 -5
- package/src/adapters/file-session-adapter.js +4 -3
- package/src/adapters/maria-adapter.js +33 -7
- package/src/adapters/mega-cache-adapter.js +83 -6
- package/src/adapters/mega-db-adapter.js +10 -1
- package/src/adapters/mongo-adapter.js +40 -8
- package/src/adapters/postgres-adapter.js +33 -6
- package/src/adapters/redis-adapter.js +7 -3
- package/src/adapters/sqlite-adapter.js +26 -3
- package/src/cli/commands/console-cmd.js +3 -1
- package/src/cli/commands/new.js +13 -3
- package/src/cli/commands/scaffold.js +173 -33
- package/src/cli/generators/index.js +140 -3
- package/src/cli/index.js +437 -155
- package/src/cli/watch.js +188 -0
- package/src/core/ajv-mapper.js +30 -3
- package/src/core/boot.js +464 -245
- package/src/core/cluster-metrics.js +13 -4
- package/src/core/ctx-builder.js +65 -3
- package/src/core/envelope.js +119 -12
- package/src/core/hub-link.js +89 -18
- package/src/core/i18n.js +11 -1
- package/src/core/index.js +7 -3
- package/src/core/mega-app.js +253 -505
- package/src/core/mega-cluster.js +4 -1
- package/src/core/mega-server.js +40 -9
- package/src/core/migration/dialect-registry.js +107 -0
- package/src/core/migration/dialects/README.md +62 -0
- package/src/core/migration/dialects/maria.js +496 -0
- package/src/core/migration/dialects/mongo.js +824 -0
- package/src/core/migration/dialects/postgres.js +563 -0
- package/src/core/migration/dialects/sqlite.js +476 -0
- package/src/core/migration/differ.js +456 -0
- package/src/core/migration/generate.js +508 -0
- package/src/core/migration/journal.js +167 -0
- package/src/core/migration/model-scan.js +84 -0
- package/src/core/migration/mongo-migration-db.js +97 -0
- package/src/core/migration/schema-builder.js +400 -0
- package/src/core/migration/schema-validator.js +315 -0
- package/src/core/migration-lock.js +205 -0
- package/src/core/migration-runner.js +166 -38
- package/src/core/multipart.js +28 -5
- package/src/core/pipeline.js +131 -0
- package/src/core/router.js +70 -65
- package/src/core/scope-registry.js +1 -0
- package/src/core/security.js +70 -12
- package/src/core/session-store.js +14 -1
- package/src/core/workers-manager.js +12 -1
- package/src/core/ws-cluster.js +10 -3
- package/src/core/ws-message.js +48 -4
- package/src/core/ws-presence.js +636 -0
- package/src/core/ws-roster.js +50 -8
- package/src/core/ws-upgrade.js +223 -12
- package/src/index.js +1 -1
- package/src/lib/hub-protocol.js +29 -0
- package/src/lib/mega-circuit-breaker.js +5 -3
- package/src/lib/mega-health.js +35 -4
- package/src/lib/mega-job-queue.js +151 -34
- package/src/lib/mega-job.js +37 -1
- package/src/lib/mega-metrics.js +31 -13
- package/src/lib/mega-plugin.js +34 -3
- package/src/lib/mega-schedule.js +40 -22
- package/src/lib/mega-shutdown.js +114 -39
- package/src/lib/mega-tracing.js +66 -19
- package/src/lib/mega-worker.js +33 -6
- package/src/lib/otel-resource.js +36 -0
- package/src/{cli → lib}/ws-hub.js +139 -15
- package/src/models/crud-sql-builder.js +133 -0
- package/src/models/mega-model.js +82 -2
- package/src/models/model-crud.js +483 -0
- package/src/models/mongo-crud.js +285 -0
- package/templates/adr/code.tpl +23 -0
- package/templates/model/code-mongo.tpl +35 -0
- package/templates/model/code.tpl +15 -1
- package/templates/model/test-mongo.tpl +38 -0
- package/templates/model/test.tpl +4 -0
- package/types/adapters/adapter-manager.d.ts +95 -0
- package/types/adapters/adapter-options.d.ts +93 -0
- package/types/adapters/file-adapter.d.ts +105 -0
- package/types/adapters/file-session-adapter.d.ts +103 -0
- package/types/adapters/index.d.ts +20 -0
- package/types/adapters/maria-adapter.d.ts +117 -0
- package/types/adapters/mega-adapter.d.ts +215 -0
- package/types/adapters/mega-bus-adapter.d.ts +45 -0
- package/types/adapters/mega-cache-adapter.d.ts +73 -0
- package/types/adapters/mega-db-adapter.d.ts +50 -0
- package/types/adapters/mega-lock-adapter.d.ts +62 -0
- package/types/adapters/mega-log-sink-adapter.d.ts +15 -0
- package/types/adapters/mega-session-adapter.d.ts +32 -0
- package/types/adapters/mongo-adapter.d.ts +150 -0
- package/types/adapters/nats-adapter.d.ts +108 -0
- package/types/adapters/postgres-adapter.d.ts +141 -0
- package/types/adapters/redis-adapter.d.ts +78 -0
- package/types/adapters/redis-session-adapter.d.ts +82 -0
- package/types/adapters/redlock-adapter.d.ts +149 -0
- package/types/adapters/registry.d.ts +46 -0
- package/types/adapters/sqlite-adapter.d.ts +112 -0
- package/types/auth/index.d.ts +24 -0
- package/types/cli/commands/console-cmd.d.ts +37 -0
- package/types/cli/commands/new.d.ts +16 -0
- package/types/cli/commands/routes.d.ts +36 -0
- package/types/cli/commands/scaffold.d.ts +78 -0
- package/types/cli/commands/test-cmd.d.ts +14 -0
- package/types/cli/generators/index.d.ts +122 -0
- package/types/cli/index.d.ts +234 -0
- package/types/cli/template-engine.d.ts +40 -0
- package/types/cli/watch.d.ts +59 -0
- package/types/core/ajv-mapper.d.ts +27 -0
- package/types/core/boot.d.ts +233 -0
- package/types/core/cluster-metrics.d.ts +52 -0
- package/types/core/config-loader.d.ts +13 -0
- package/types/core/config-validator.d.ts +30 -0
- package/types/core/ctx-builder.d.ts +103 -0
- package/types/core/envelope.d.ts +79 -0
- package/types/core/error-mapper.d.ts +17 -0
- package/types/core/formbody.d.ts +41 -0
- package/types/core/hub-link.d.ts +266 -0
- package/types/core/i18n.d.ts +178 -0
- package/types/core/index.d.ts +28 -0
- package/types/core/mega-app.d.ts +529 -0
- package/types/core/mega-cluster.d.ts +104 -0
- package/types/core/mega-server.d.ts +91 -0
- package/types/core/mega-service.d.ts +31 -0
- package/types/core/migration/dialect-registry.d.ts +22 -0
- package/types/core/migration/dialects/maria.d.ts +99 -0
- package/types/core/migration/dialects/mongo.d.ts +89 -0
- package/types/core/migration/dialects/postgres.d.ts +117 -0
- package/types/core/migration/dialects/sqlite.d.ts +111 -0
- package/types/core/migration/differ.d.ts +47 -0
- package/types/core/migration/generate.d.ts +56 -0
- package/types/core/migration/journal.d.ts +52 -0
- package/types/core/migration/model-scan.d.ts +19 -0
- package/types/core/migration/mongo-migration-db.d.ts +7 -0
- package/types/core/migration/schema-builder.d.ts +197 -0
- package/types/core/migration/schema-validator.d.ts +20 -0
- package/types/core/migration-lock.d.ts +33 -0
- package/types/core/migration-runner.d.ts +101 -0
- package/types/core/multipart.d.ts +86 -0
- package/types/core/openapi.d.ts +62 -0
- package/types/core/pipeline.d.ts +93 -0
- package/types/core/router.d.ts +159 -0
- package/types/core/routes-loader.d.ts +21 -0
- package/types/core/scope-registry.d.ts +14 -0
- package/types/core/security.d.ts +77 -0
- package/types/core/services-loader.d.ts +27 -0
- package/types/core/session-cleanup-schedule.d.ts +19 -0
- package/types/core/session-store.d.ts +25 -0
- package/types/core/session.d.ts +77 -0
- package/types/core/static-assets.d.ts +73 -0
- package/types/core/template.d.ts +106 -0
- package/types/core/workers-manager.d.ts +79 -0
- package/types/core/ws-cluster.d.ts +208 -0
- package/types/core/ws-compression.d.ts +112 -0
- package/types/core/ws-controller.d.ts +65 -0
- package/types/core/ws-message.d.ts +106 -0
- package/types/core/ws-presence.d.ts +273 -0
- package/types/core/ws-roster.d.ts +108 -0
- package/types/core/ws-upgrade.d.ts +260 -0
- package/types/errors/config-error.d.ts +10 -0
- package/types/errors/http-errors.d.ts +120 -0
- package/types/errors/index.d.ts +3 -0
- package/types/errors/mega-error.d.ts +32 -0
- package/types/index.d.ts +39 -0
- package/types/lib/asp/config.d.ts +49 -0
- package/types/lib/asp/crypto.d.ts +43 -0
- package/types/lib/asp/errors.d.ts +30 -0
- package/types/lib/asp/nonce-cache.d.ts +52 -0
- package/types/lib/asp/plugin.d.ts +30 -0
- package/types/lib/asp/ws-terminator.d.ts +45 -0
- package/types/lib/env-mapper.d.ts +14 -0
- package/types/lib/hub-protocol.d.ts +106 -0
- package/types/lib/index.d.ts +22 -0
- package/types/lib/logger/telegram-core.d.ts +104 -0
- package/types/lib/logger/telegram-transport.d.ts +45 -0
- package/types/lib/mega-brute-force.d.ts +66 -0
- package/types/lib/mega-circuit-breaker.d.ts +243 -0
- package/types/lib/mega-cron.d.ts +66 -0
- package/types/lib/mega-hash.d.ts +32 -0
- package/types/lib/mega-health.d.ts +48 -0
- package/types/lib/mega-job-queue.d.ts +188 -0
- package/types/lib/mega-job-worker.d.ts +130 -0
- package/types/lib/mega-job.d.ts +145 -0
- package/types/lib/mega-logger.d.ts +45 -0
- package/types/lib/mega-metrics.d.ts +285 -0
- package/types/lib/mega-plugin.d.ts +245 -0
- package/types/lib/mega-retry.d.ts +85 -0
- package/types/lib/mega-schedule.d.ts +260 -0
- package/types/lib/mega-shutdown.d.ts +135 -0
- package/types/lib/mega-tracing.d.ts +224 -0
- package/types/lib/mega-worker.d.ts +129 -0
- package/types/lib/otel-resource.d.ts +16 -0
- package/types/lib/worker-runner/process-entry.d.ts +1 -0
- package/types/lib/worker-runner/task-dispatch.d.ts +28 -0
- package/types/lib/worker-runner/thread-entry.d.ts +1 -0
- package/types/lib/ws-hub.d.ts +259 -0
- package/types/models/crud-sql-builder.d.ts +48 -0
- package/types/models/index.d.ts +1 -0
- package/types/models/mega-model.d.ts +138 -0
- package/types/models/model-crud.d.ts +82 -0
- package/types/models/mongo-crud.d.ts +59 -0
- package/types/test/index.d.ts +84 -0
- package/.env +0 -127
- package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +0 -30
- package/sample/crud/apps/main/models/note.js +0 -71
- package/sample/crud/apps/main/models/user.js +0 -86
- package/sample/crud/package-lock.json +0 -5665
- package/sample/crud/yarn.lock +0 -2142
- package/sample/simple/package-lock.json +0 -1851
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
export class MegaWsPresence {
|
|
2
|
+
/**
|
|
3
|
+
* @param {Object} opts
|
|
4
|
+
* @param {string} opts.appName - 소유 앱 이름(hook 이름·로그 식별자).
|
|
5
|
+
* @param {import('pino').Logger | { debug?: Function, warn?: Function } | null} [opts.log] -
|
|
6
|
+
* 앱 공유 로거(보통 `app.fastify.log`). 미주입이면 무로그.
|
|
7
|
+
*/
|
|
8
|
+
constructor({ appName, log }?: {
|
|
9
|
+
appName: string;
|
|
10
|
+
log?: import("pino").Logger | {
|
|
11
|
+
debug?: Function;
|
|
12
|
+
warn?: Function;
|
|
13
|
+
} | null;
|
|
14
|
+
});
|
|
15
|
+
/** @type {string} */
|
|
16
|
+
_appName: string;
|
|
17
|
+
/** @type {any} */
|
|
18
|
+
_log: any;
|
|
19
|
+
/** @type {MegaHubLink|null} hub 연결 (scaffold 권장). */
|
|
20
|
+
_hubLink: MegaHubLink | null;
|
|
21
|
+
/** @type {import('./ws-cluster.js').MegaWsCluster|null} NATS 기반 클러스터 fan-out/roster (ADR-176, boot 자동배선). */
|
|
22
|
+
_wsCluster: import("./ws-cluster.js").MegaWsCluster | null;
|
|
23
|
+
/** @type {import('./ws-roster.js').MegaWsRedisRoster|null} 채널별 redis roster(ADR-177, boot 자동배선).
|
|
24
|
+
* 접속자 목록(상태)을 redis HASH 로 cluster-wide 관리한다(broadcast 와 별개 — 멀티 허브 정합·즉시 스냅샷). */
|
|
25
|
+
_wsRoster: import("./ws-roster.js").MegaWsRedisRoster | null;
|
|
26
|
+
/** ns(=ws path) → 활성 로컬 연결 집합 (hub broadcast 의 local fan-out 용). @type {Map<string, Set<import('./ws-upgrade.js').MegaWsConnection>>} */
|
|
27
|
+
_wsConns: Map<string, Set<import("./ws-upgrade.js").MegaWsConnection>>;
|
|
28
|
+
/** userId → 활성 로컬 연결 집합 (DIRECT 타겟팅, H-latent guard). @type {Map<string, Set<import('./ws-upgrade.js').MegaWsConnection>>} */
|
|
29
|
+
_userConns: Map<string, Set<import("./ws-upgrade.js").MegaWsConnection>>;
|
|
30
|
+
/** sessionId → 활성 로컬 연결 1개 (세션단위 JOIN/LEAVE). @type {Map<string, import('./ws-upgrade.js').MegaWsConnection>} */
|
|
31
|
+
_sessionConns: Map<string, import("./ws-upgrade.js").MegaWsConnection>;
|
|
32
|
+
/** @type {string[]} connectHub 으로 구독한 채널. */
|
|
33
|
+
_hubChannels: string[];
|
|
34
|
+
/** connectHub 의 bridgeId (재연결 재구독·세션 JOIN 에 재사용). @type {string|null} */
|
|
35
|
+
_hubBridgeId: string | null;
|
|
36
|
+
/** 현재 연결된 hub link (미연결 시 null). */
|
|
37
|
+
get hubLink(): MegaHubLink;
|
|
38
|
+
/**
|
|
39
|
+
* 이 bridge 를 hub 에 연결한다 (ADR-033/059). REGISTER 핸드셰이크 완료 후
|
|
40
|
+
* 선언 채널을 구독(bridge-subscriber JOIN)하고, hub 의 BROADCAST/DIRECT 를 로컬 소켓에 전달한다.
|
|
41
|
+
*
|
|
42
|
+
* single 모드는 embedded 종단이라 hub 가 필수는 아니지만(02-architecture §3), 멀티 인스턴스
|
|
43
|
+
* fan-out 이 필요하면 single 에서도 사용 가능하다. scaffold(멀티앱/클러스터)에서 권장.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} config - MegaBridgeHubConfig (§2.1) + 구독 채널.
|
|
46
|
+
* @param {string} config.url - hub URL.
|
|
47
|
+
* @param {string} config.token - Bearer 토큰.
|
|
48
|
+
* @param {string} config.bridgeId - 운영 식별자.
|
|
49
|
+
* @param {string} [config.instanceId]
|
|
50
|
+
* @param {string[]} [config.capabilities]
|
|
51
|
+
* @param {string[]} [config.channels] - 자동 구독할 채널 목록.
|
|
52
|
+
* @param {import('../lib/mega-retry.js').MegaRetryOptions} [config.retry] - 지정 시 재연결 활성(ADR-098).
|
|
53
|
+
* hub 재시작·drain(4503)·네트워크 단절 시 지수 백오프로 재연결하고, 성공하면 presence(채널·세션
|
|
54
|
+
* JOIN)를 자동 재동기화한다(hub 는 절단 시점 presence 를 잃으므로).
|
|
55
|
+
* @param {import('./ws-compression.js').WsCompressionConfig} [config.compression] - Bridge↔Hub
|
|
56
|
+
* link 압축(ADR-078 / MegaWsHubCompressionConfig). Global `wsHub.compression`
|
|
57
|
+
* 블록을 그대로 전달한다 — hub 서버와 같은 스키마. 디폴트 OFF. 잘못된 threshold/windowBits 면
|
|
58
|
+
* 즉시 throw(부팅 fail-fast).
|
|
59
|
+
* @returns {Promise<MegaHubLink>} 등록 완료된 link.
|
|
60
|
+
*/
|
|
61
|
+
connectHub(config?: {
|
|
62
|
+
url: string;
|
|
63
|
+
token: string;
|
|
64
|
+
bridgeId: string;
|
|
65
|
+
instanceId?: string;
|
|
66
|
+
capabilities?: string[];
|
|
67
|
+
channels?: string[];
|
|
68
|
+
retry?: import("../lib/mega-retry.js").MegaRetryOptions;
|
|
69
|
+
compression?: import("./ws-compression.js").WsCompressionConfig;
|
|
70
|
+
}): Promise<MegaHubLink>;
|
|
71
|
+
/**
|
|
72
|
+
* hub presence 재동기화 — bridge-subscriber 채널 JOIN + 활성 사용자 세션 JOIN 을 모두 다시 보낸다.
|
|
73
|
+
* 최초 등록 직후와 재연결(RECONNECTED) 직후에 호출된다. hub 의 JOIN 처리는 멱등(같은 sessionId 덮어씀).
|
|
74
|
+
* @returns {void}
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
private _resyncPresence;
|
|
78
|
+
/**
|
|
79
|
+
* 실 사용자 세션을 hub presence 에 등록하고 로컬 매핑을 만든다 (OQ-010/ADR-098).
|
|
80
|
+
*
|
|
81
|
+
* 표준 패턴: WS upgrade 의 `before` 미들웨어가 인증 후 신원을 `ctx.auth` 로 싣고(ADR-091 DI),
|
|
82
|
+
* 채널의 `onConnect(sock, ctx)` 에서 `ctx.app.joinSession(sock, { userId: ctx.auth.userId, ... })`
|
|
83
|
+
* 를 호출한다. 이 매핑이 있어야 DIRECT 가 **해당 userId 세션에만** 전달된다(cross-user flood 방지,
|
|
84
|
+
* H-latent guard). 매핑 없는 연결은 DIRECT 대상에서 제외된다.
|
|
85
|
+
*
|
|
86
|
+
* @param {import('./ws-upgrade.js').MegaWsConnection} conn - onConnect 가 받은 소켓 래퍼.
|
|
87
|
+
* @param {Object} entry
|
|
88
|
+
* @param {string} entry.userId - 인증된 사용자 식별자(비어 있으면 throw).
|
|
89
|
+
* @param {string} entry.sessionId - 세션 식별자(비어 있으면 throw). 전역 유일 권장.
|
|
90
|
+
* @param {string[]} [entry.channels] - 가입 채널 목록.
|
|
91
|
+
* @param {Object} [entry.metadata] - presence 메타데이터(명시 필드만, ADR-059).
|
|
92
|
+
* @returns {this}
|
|
93
|
+
* @throws {Error} conn/userId/sessionId 누락 시 — 잘못된 매핑을 silent 통과시키지 않는다.
|
|
94
|
+
*/
|
|
95
|
+
joinSession(conn: import("./ws-upgrade.js").MegaWsConnection, { userId, sessionId, channels, metadata }?: {
|
|
96
|
+
userId: string;
|
|
97
|
+
sessionId: string;
|
|
98
|
+
channels?: string[];
|
|
99
|
+
metadata?: Object;
|
|
100
|
+
}): this;
|
|
101
|
+
/**
|
|
102
|
+
* 채널 broadcast — 로컬 ns 소켓에 즉시 전달 + (hub 연결 시) 클러스터 fan-out.
|
|
103
|
+
*
|
|
104
|
+
* @param {{ ns: string, channel: string, message: { type: string, payload?: Object }, exceptSessionIds?: string[] }} args
|
|
105
|
+
* @returns {void}
|
|
106
|
+
* @throws {Error} message.type(string) 누락 시 — 호출부 입력 오류를 silent drop 하지 않고 즉시 알린다(L6).
|
|
107
|
+
*/
|
|
108
|
+
broadcast({ ns, channel, message, exceptSessionIds }: {
|
|
109
|
+
ns: string;
|
|
110
|
+
channel: string;
|
|
111
|
+
message: {
|
|
112
|
+
type: string;
|
|
113
|
+
payload?: Object;
|
|
114
|
+
};
|
|
115
|
+
exceptSessionIds?: string[];
|
|
116
|
+
}): void;
|
|
117
|
+
/**
|
|
118
|
+
* 특정 사용자에게 직접 전송 (directToUser, ADR-035) — 로컬에서 그 userId 로 매핑된 연결에만 전달
|
|
119
|
+
* (H-latent guard) + (hub 연결 시) 클러스터 fan-out(다른 bridge 의 같은 userId 세션까지).
|
|
120
|
+
*
|
|
121
|
+
* {@link MegaWsPresence#joinSession} 으로 매핑된 연결만 대상이다 — 매핑 없는 userId 면 로컬 no-op.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} userId - 대상 사용자.
|
|
124
|
+
* @param {{ type: string, payload?: Object }} message - 내부 envelope `{ type, payload }`.
|
|
125
|
+
* @returns {void}
|
|
126
|
+
* @throws {Error} userId/message.type 누락 시(broadcast 와 동일한 입력 보호, L6).
|
|
127
|
+
*/
|
|
128
|
+
directToUser(userId: string, message: {
|
|
129
|
+
type: string;
|
|
130
|
+
payload?: Object;
|
|
131
|
+
}): void;
|
|
132
|
+
/**
|
|
133
|
+
* 세션 presence 메타데이터 갱신 (METADATA, ADR-059) — 로컬 conn 에 저장 + (hub 연결 시) 전파.
|
|
134
|
+
*
|
|
135
|
+
* 로컬 conn 의 `metadata` 를 갱신해 두면 이후 재연결 시 {@link MegaWsPresence#_resyncPresence} 가 최신
|
|
136
|
+
* 메타까지 복구한다(M-1). 매핑 없는 sessionId 면 no-op(로컬 저장 없이 hub 전파만은 하지 않음 —
|
|
137
|
+
* 재연결 보존 대상이 없으므로). broadcast/directToUser 와 같은 best-effort fan-out.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} sessionId - 대상 세션(joinSession 으로 매핑된 것).
|
|
140
|
+
* @param {Object} metadata - 갱신할 메타데이터(명시 필드만).
|
|
141
|
+
* @returns {this}
|
|
142
|
+
* @throws {Error} sessionId/metadata 누락 시(입력 보호, L6 와 동일 원칙).
|
|
143
|
+
*/
|
|
144
|
+
updateMetadata(sessionId: string, metadata: Object): this;
|
|
145
|
+
/**
|
|
146
|
+
* NATS 클러스터 fan-out/roster 를 이 앱에 배선한다 (ADR-176). boot 가 `wsCluster` config 를 보고
|
|
147
|
+
* 자동 호출하므로 개발자가 직접 부를 일은 없다(테스트·고급 용도로만 공개).
|
|
148
|
+
* @param {import('./ws-cluster.js').MegaWsCluster|null} cluster
|
|
149
|
+
* @returns {this}
|
|
150
|
+
*/
|
|
151
|
+
setWsCluster(cluster: import("./ws-cluster.js").MegaWsCluster | null): this;
|
|
152
|
+
/**
|
|
153
|
+
* 해당 ns(WS 채널 경로)의 **클러스터 전역 접속자 목록**을 반환한다 (ADR-176). `wsCluster` 자동배선 +
|
|
154
|
+
* `joinSession`/disconnect 훅으로 프레임워크가 동기화하므로 개발자는 roster 코드를 짜지 않고 읽기만 한다.
|
|
155
|
+
* wsCluster 미배선(또는 roster:'none')이면 로컬 멤버만 반환한다.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} ns - WS namespace(채널 경로, 예: '/ws/chat').
|
|
158
|
+
* @returns {Array<{ sessionId: string, userId: string, metadata?: Object }>}
|
|
159
|
+
*/
|
|
160
|
+
roster(ns: string): Array<{
|
|
161
|
+
sessionId: string;
|
|
162
|
+
userId: string;
|
|
163
|
+
metadata?: Object;
|
|
164
|
+
}>;
|
|
165
|
+
/**
|
|
166
|
+
* 채널별 redis roster(ADR-177)를 이 앱에 배선한다. boot 가 `bridgeHub.roster.driver==='redis'` 일 때
|
|
167
|
+
* 자동 호출하므로 개발자가 직접 부를 일은 없다(테스트·고급 용도로만 공개).
|
|
168
|
+
* @param {import('./ws-roster.js').MegaWsRedisRoster|null} roster
|
|
169
|
+
* @returns {this}
|
|
170
|
+
*/
|
|
171
|
+
setWsRoster(roster: import("./ws-roster.js").MegaWsRedisRoster | null): this;
|
|
172
|
+
/**
|
|
173
|
+
* 주어진 **채널들**의 cluster-wide 접속자 목록 — redis roster(원격 포함) + 로컬 세션을 병합한다(ADR-177).
|
|
174
|
+
* 로컬을 항상 포함해 join 직후 redis HSET 반영 전에도 본인이 빠지지 않게 한다. `ctx.presence.list()` 의
|
|
175
|
+
* redis 경로가 호출(async). roster 미배선이면 로컬 채널 멤버만.
|
|
176
|
+
* @param {string[]} channels
|
|
177
|
+
* @returns {Promise<Array<{ sessionId: string, userId: string, metadata?: Object }>>}
|
|
178
|
+
*/
|
|
179
|
+
presenceList(channels: string[]): Promise<Array<{
|
|
180
|
+
sessionId: string;
|
|
181
|
+
userId: string;
|
|
182
|
+
metadata?: Object;
|
|
183
|
+
}>>;
|
|
184
|
+
/**
|
|
185
|
+
* 이 워커의 로컬 멤버 목록 — redis roster heartbeat 갱신 대상(ADR-177). joinSession 으로 매핑된
|
|
186
|
+
* (채널 × 세션)마다 1개. MegaWsRedisRoster 가 주기적으로 이 목록의 expiresAt 을 갱신한다.
|
|
187
|
+
* @returns {Array<{ channel: string, sessionId: string, member: { userId: string, metadata?: Object } }>}
|
|
188
|
+
*/
|
|
189
|
+
localRosterMembers(): Array<{
|
|
190
|
+
channel: string;
|
|
191
|
+
sessionId: string;
|
|
192
|
+
member: {
|
|
193
|
+
userId: string;
|
|
194
|
+
metadata?: Object;
|
|
195
|
+
};
|
|
196
|
+
}>;
|
|
197
|
+
/**
|
|
198
|
+
* broadcast payload 를 로컬 ns 소켓에 전달한다. message 는 `{ type, payload }` 내부 envelope.
|
|
199
|
+
*
|
|
200
|
+
* `exceptSessionIds` 에 든 sessionId 로 매핑된 연결은 제외한다(ADR-098). 세션 매핑이 없는
|
|
201
|
+
* 연결(zero-config·미JOIN)은 sessionId 가 없어 제외 대상에 걸리지 않으므로 그대로 받는다.
|
|
202
|
+
*
|
|
203
|
+
* @param {{ ns: string, channel?: string, message: { type: string, payload?: Object }, exceptSessionIds?: string[] }} payload
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
206
|
+
_deliverBroadcast({ ns, message, exceptSessionIds }: {
|
|
207
|
+
ns: string;
|
|
208
|
+
channel?: string;
|
|
209
|
+
message: {
|
|
210
|
+
type: string;
|
|
211
|
+
payload?: Object;
|
|
212
|
+
};
|
|
213
|
+
exceptSessionIds?: string[];
|
|
214
|
+
}): void;
|
|
215
|
+
/**
|
|
216
|
+
* direct payload 를 **해당 userId 로 매핑된 로컬 연결에만** 전달한다 (H-latent guard).
|
|
217
|
+
*
|
|
218
|
+
* 초기에는 매핑이 없어 모든 연결에 flood 됐다(cross-user 누출). {@link MegaWsPresence#joinSession}
|
|
219
|
+
* 으로 만든 `userId → 연결` 매핑을 통해 대상 사용자에게만 보낸다. 매핑이 없는 userId 면 no-op
|
|
220
|
+
* (다른 사용자에게 새지 않음).
|
|
221
|
+
*
|
|
222
|
+
* @param {{ userId: string, message: { type: string, payload?: Object } }} payload
|
|
223
|
+
* @returns {void}
|
|
224
|
+
*/
|
|
225
|
+
_deliverDirect({ userId, message }: {
|
|
226
|
+
userId: string;
|
|
227
|
+
message: {
|
|
228
|
+
type: string;
|
|
229
|
+
payload?: Object;
|
|
230
|
+
};
|
|
231
|
+
}): void;
|
|
232
|
+
/**
|
|
233
|
+
* hub 가 라우팅한 DISCONNECT(admin-kick/재배치, ADR-097)를 처리한다 — sessionId 로 매핑된 로컬
|
|
234
|
+
* 소켓을 닫는다. `requeue` 로 두 시맨틱이 갈린다:
|
|
235
|
+
*
|
|
236
|
+
* - `requeue: false`(기본) = **kick** — RFC 6455 `1008`(policy violation, hub Bearer 거부와 동일
|
|
237
|
+
* 코드). "돌아오지 마라" — 클라이언트는 재연결하지 않아야 한다.
|
|
238
|
+
* - `requeue: true` = **우아한 재배치** — `4503`({@link import('./ws-upgrade.js').CLOSE_CODE_REQUEUE},
|
|
239
|
+
* bridge↔hub drain 과 동일 규약). "세션 유지한 채 즉시 재연결하라" — HTTP 세션은 그대로이므로
|
|
240
|
+
* 재연결이 LB 를 거쳐 다른 워커에 닿아도 `before` 인증이 세션 쿠키로 자연 통과한다
|
|
241
|
+
* (transparent re-route — 워커 drain·리밸런싱용).
|
|
242
|
+
*
|
|
243
|
+
* reason 은 WS close frame 한도(123 bytes)에 맞춰 자른다.
|
|
244
|
+
*
|
|
245
|
+
* @param {{ sessionId: string, reason?: string, requeue?: boolean }} payload
|
|
246
|
+
* @returns {void}
|
|
247
|
+
*/
|
|
248
|
+
_handleHubDisconnect({ sessionId, reason, requeue }?: {
|
|
249
|
+
sessionId: string;
|
|
250
|
+
reason?: string;
|
|
251
|
+
requeue?: boolean;
|
|
252
|
+
}): void;
|
|
253
|
+
/**
|
|
254
|
+
* 로컬 WS 연결 등록 (driveWsConnection 이 호출 — framework-internal). hub broadcast 의 local 전달 대상.
|
|
255
|
+
* @param {import('./ws-upgrade.js').MegaWsConnection} conn
|
|
256
|
+
* @returns {void}
|
|
257
|
+
*/
|
|
258
|
+
_trackWsConn(conn: import("./ws-upgrade.js").MegaWsConnection): void;
|
|
259
|
+
/**
|
|
260
|
+
* 로컬 WS 연결 해제 (close 시 driveWsConnection 이 호출 — framework-internal).
|
|
261
|
+
* @param {import('./ws-upgrade.js').MegaWsConnection} conn
|
|
262
|
+
* @returns {void}
|
|
263
|
+
*/
|
|
264
|
+
_untrackWsConn(conn: import("./ws-upgrade.js").MegaWsConnection): void;
|
|
265
|
+
/**
|
|
266
|
+
* presence 전체 정리 — hub link → NATS cluster → redis roster → 연결 인덱스 순.
|
|
267
|
+
* 각 자원의 shutdown hook 도 짝맞춰 해제한다(닫힌 앱의 hook 잔존·중복 정리 방지).
|
|
268
|
+
* MegaApp.close 가 WS 클라이언트 종료(1001) **전에** 호출한다.
|
|
269
|
+
* @returns {Promise<void>}
|
|
270
|
+
*/
|
|
271
|
+
close(): Promise<void>;
|
|
272
|
+
}
|
|
273
|
+
import { MegaHubLink } from './hub-link.js';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 채널 roster 멤버(redis 에 저장되는 형태 — sessionId 는 HASH field 라 값엔 안 넣음).
|
|
3
|
+
* @typedef {Object} RosterMember
|
|
4
|
+
* @property {string} userId
|
|
5
|
+
* @property {Object} [metadata]
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* redis 기반 채널별 roster.
|
|
9
|
+
*
|
|
10
|
+
* 앱(MegaApp) 1개당 1 인스턴스. boot 가 `bridgeHub.roster.driver==='redis'` 일 때 생성·start·shutdown 한다.
|
|
11
|
+
* redis 핸들은 캐시 어댑터의 `.native`(ioredis) 를 받는다 — pub/sub 가 아니라 HASH 명령(HSET/HGETALL/HDEL)만 쓴다.
|
|
12
|
+
*/
|
|
13
|
+
export class MegaWsRedisRoster {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} opts
|
|
16
|
+
* @param {import('ioredis').Redis} opts.redis - 캐시 어댑터의 raw ioredis 핸들.
|
|
17
|
+
* @param {() => Array<{ channel: string, sessionId: string, member: RosterMember }>} opts.getLocalMembers -
|
|
18
|
+
* 이 워커의 현재 로컬 멤버 목록(heartbeat 갱신 대상). 보통 mega-app 의 세션 매핑에서 도출.
|
|
19
|
+
* @param {string} [opts.keyPrefix] - 키 접두(기본 'ws:roster').
|
|
20
|
+
* @param {number} [opts.ttlMs] - 멤버 TTL(ms, 기본 30000).
|
|
21
|
+
* @param {{ debug?: Function, warn?: Function, error?: Function }} [opts.logger]
|
|
22
|
+
*/
|
|
23
|
+
constructor({ redis, getLocalMembers, keyPrefix, ttlMs, logger }?: {
|
|
24
|
+
redis: import("ioredis").Redis;
|
|
25
|
+
getLocalMembers: () => Array<{
|
|
26
|
+
channel: string;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
member: RosterMember;
|
|
29
|
+
}>;
|
|
30
|
+
keyPrefix?: string;
|
|
31
|
+
ttlMs?: number;
|
|
32
|
+
logger?: {
|
|
33
|
+
debug?: Function;
|
|
34
|
+
warn?: Function;
|
|
35
|
+
error?: Function;
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
_redis: import("ioredis").default;
|
|
39
|
+
_getLocalMembers: () => Array<{
|
|
40
|
+
channel: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
member: RosterMember;
|
|
43
|
+
}>;
|
|
44
|
+
_prefix: string;
|
|
45
|
+
_ttlMs: number;
|
|
46
|
+
_log: {
|
|
47
|
+
debug?: Function;
|
|
48
|
+
warn?: Function;
|
|
49
|
+
error?: Function;
|
|
50
|
+
};
|
|
51
|
+
/** @type {ReturnType<typeof setInterval>|null} */
|
|
52
|
+
_hbTimer: ReturnType<typeof setInterval> | null;
|
|
53
|
+
/** 채널 → redis 키. @param {string} channel @returns {string} @private */
|
|
54
|
+
private _key;
|
|
55
|
+
/**
|
|
56
|
+
* 채널 roster 에 멤버 추가/갱신(joinSession). 키 자체에도 EXPIRE 를 걸어 버려진 채널이 새지 않게 한다.
|
|
57
|
+
* @param {string} channel @param {string} sessionId @param {RosterMember} member @returns {Promise<void>}
|
|
58
|
+
*/
|
|
59
|
+
add(channel: string, sessionId: string, member: RosterMember): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* ioredis pipeline.exec() 결과(`[err, res][]`)에서 첫 명령 오류를 throw 한다 — pipeline 은 명령별
|
|
62
|
+
* 오류를 reject 가 아니라 결과 배열에 싣기 때문에, 묵히면 호출부의 기존 실패 처리(catch+warn)가
|
|
63
|
+
* 더는 동작하지 않는다(직렬 await 시절의 throw 계약 유지).
|
|
64
|
+
* @param {Array<[Error | null, any]> | null} results
|
|
65
|
+
* @returns {void}
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
private _throwFirstPipelineError;
|
|
69
|
+
/**
|
|
70
|
+
* 채널 roster 에서 멤버 제거(disconnect).
|
|
71
|
+
* @param {string} channel @param {string} sessionId @returns {Promise<void>}
|
|
72
|
+
*/
|
|
73
|
+
remove(channel: string, sessionId: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* 채널의 **현재 접속자 목록**(만료 제외). 만료 엔트리는 best-effort 로 정리한다.
|
|
76
|
+
* @param {string} channel @returns {Promise<Array<{ sessionId: string, userId: string, metadata?: Object }>>}
|
|
77
|
+
*/
|
|
78
|
+
list(channel: string): Promise<Array<{
|
|
79
|
+
sessionId: string;
|
|
80
|
+
userId: string;
|
|
81
|
+
metadata?: Object;
|
|
82
|
+
}>>;
|
|
83
|
+
/**
|
|
84
|
+
* heartbeat 시작 — 주기적으로 로컬 멤버의 `expiresAt` 을 갱신해 살아있음을 알린다(crash 워커는 갱신이
|
|
85
|
+
* 끊겨 만료). 프로세스 종료를 막지 않게 unref. 멱등(중복 호출 무시).
|
|
86
|
+
* @returns {void}
|
|
87
|
+
*/
|
|
88
|
+
startHeartbeat(): void;
|
|
89
|
+
/**
|
|
90
|
+
* 로컬 멤버 전체의 `expiresAt` 을 한 pipeline 으로 일괄 갱신. 직렬 add 루프(멤버당 2 RT)는 멤버
|
|
91
|
+
* 1,000 에 주기당 수백 ms — 갱신 지연이 TTL 윈도를 침식해 살아있는 세션이 lazy 만료될 수 있었다.
|
|
92
|
+
* pipeline 이면 단일 왕복 수준(실측 ~200×). 채널 키 PEXPIRE 는 채널당 1회만 싣는다.
|
|
93
|
+
* @returns {Promise<void>} @private
|
|
94
|
+
*/
|
|
95
|
+
private _refreshLocal;
|
|
96
|
+
/**
|
|
97
|
+
* 정리 — heartbeat 중지 + 로컬 멤버를 redis 에서 제거(graceful 종료 시 다른 워커가 즉시 정합).
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
100
|
+
stop(): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 채널 roster 멤버(redis 에 저장되는 형태 — sessionId 는 HASH field 라 값엔 안 넣음).
|
|
104
|
+
*/
|
|
105
|
+
export type RosterMember = {
|
|
106
|
+
userId: string;
|
|
107
|
+
metadata?: Object;
|
|
108
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 프로세스 합산 send budget 을 조정한다(ADR-215). 부팅/운영 튜닝 진입점.
|
|
3
|
+
* @param {{ maxTotalBufferedBytes?: number }} [opts] - 바이트 budget. `0` = 무제한(옵트아웃).
|
|
4
|
+
* @returns {{ maxTotalBufferedBytes: number }} 적용된 현재 값.
|
|
5
|
+
* @throws {TypeError} 음수/비숫자 — 설정 실수 fail-fast.
|
|
6
|
+
*/
|
|
7
|
+
export function configureWsSendBudget({ maxTotalBufferedBytes: max }?: {
|
|
8
|
+
maxTotalBufferedBytes?: number;
|
|
9
|
+
}): {
|
|
10
|
+
maxTotalBufferedBytes: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* 테스트 격리용 — budget 상태 초기화(추적 Set·스로틀·budget 디폴트 복원).
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
export function _resetWsSendBudget(): void;
|
|
17
|
+
/** 테스트용 — 연결을 budget 추적에 등록. @param {MegaWsConnection} conn @returns {void} */
|
|
18
|
+
export function _trackWsSendBudget(conn: MegaWsConnection): void;
|
|
19
|
+
/** 테스트용 — 스로틀 우회 가능한 스윕 직접 호출. @param {number} [now] @returns {void} */
|
|
20
|
+
export function _sweepWsSendBudget(now?: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* 라우트 opts 의 `heartbeatMs` 를 검증해 반환한다. 미지정 → 기본 30초, `0` = liveness 끔.
|
|
23
|
+
* 무효값(음수/비정수/비숫자)은 운영 실수 — 조용히 보정하지 않고 warn 로그 후 기본값을 쓴다
|
|
24
|
+
* (연결 구동 시점이라 throw 하면 핸드셰이크 콜백 밖으로 새므로 로그+기본값이 안전한 fail-safe).
|
|
25
|
+
*
|
|
26
|
+
* @param {{ heartbeatMs?: number } | undefined} opts - WS 라우트 opts.
|
|
27
|
+
* @param {any} [log] - 로거(warn).
|
|
28
|
+
* @returns {number} 적용할 주기(ms). 0 = 끔.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveWsHeartbeatMs(opts: {
|
|
31
|
+
heartbeatMs?: number;
|
|
32
|
+
} | undefined, log?: any): number;
|
|
33
|
+
/**
|
|
34
|
+
* WS 프레임 코덱 — 평문/암호 와이어 변환을 추상화한다.
|
|
35
|
+
* @typedef {Object} WsFrameCodec
|
|
36
|
+
* @property {(frame: string) => string} decode - 수신 프레임 → 평문 JSON 문자열 (실패 시 throw).
|
|
37
|
+
* @property {(json: string) => string} encode - 평문 JSON → 송신 프레임 (기본: ASP 활성 시 암호).
|
|
38
|
+
* @property {(json: string) => string} encodePlain - 평문 JSON → 평문 송신 프레임 (asp.error 통지용).
|
|
39
|
+
* @property {boolean} isEncrypted - 기본 송신이 암호화인지.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* ASP 미설정 앱용 평문 코덱 — prefix 없는 raw JSON 그대로.
|
|
43
|
+
* @returns {WsFrameCodec}
|
|
44
|
+
*/
|
|
45
|
+
export function createPlainCodec(): WsFrameCodec;
|
|
46
|
+
/**
|
|
47
|
+
* ASP 활성/비활성 namespace 용 코덱 — {@link MegaAspTerminator} 위임 (`E:`/`P:` prefix).
|
|
48
|
+
* @param {import('../lib/asp/ws-terminator.js').MegaAspTerminator} terminator
|
|
49
|
+
* @returns {WsFrameCodec}
|
|
50
|
+
*/
|
|
51
|
+
export function createAspCodec(terminator: import("../lib/asp/ws-terminator.js").MegaAspTerminator): WsFrameCodec;
|
|
52
|
+
/**
|
|
53
|
+
* 핸드셰이크 완료된 raw 소켓에 채널 라이프사이클을 구동한다.
|
|
54
|
+
*
|
|
55
|
+
* `onConnect` 1회 → 매 message `dispatch` (코덱 decode → envelope 검증 → type 디스패치) →
|
|
56
|
+
* close 시 `onDisconnect`. 모든 단계는 fail-closed (ADR-084) + 명시적 로그.
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} args
|
|
59
|
+
* @param {import('ws').WebSocket} args.raw - 핸드셰이크된 ws 소켓.
|
|
60
|
+
* @param {import('node:http').IncomingMessage} args.req - upgrade 요청 (ctx 용).
|
|
61
|
+
* @param {{ path: string, ns?: string, ChannelClass: Function, opts: Object, schemaValidators?: Record<string, import('ajv').ValidateFunction> | null }} args.route
|
|
62
|
+
* @param {import('./mega-app.js').MegaApp} args.app
|
|
63
|
+
* @param {WsFrameCodec} args.codec
|
|
64
|
+
* @param {any} args.log - request 로거 (debug/warn/error).
|
|
65
|
+
* @param {any} [args.auth] - `before` 미들웨어가 인증 후 돌려준 신원(`{ userId, sessionId, ... }`).
|
|
66
|
+
* `ctx.auth` 로 노출 — onConnect 에서 `app.joinSession(sock, { userId: ctx.auth.userId, ... })` 에 쓴다.
|
|
67
|
+
* @param {number} [args.protocolVersion] - 핸드셰이크에서 협상된 envelope 버전(`mega.v<N>` subprotocol,
|
|
68
|
+
* {@link import('./ws-message.js').negotiateWsProtocol}). 미지정 = v1(레거시). 이 연결의 envelope
|
|
69
|
+
* 검증 기준이 되고 `conn.protocolVersion`/`ctx.protocolVersion` 으로 노출된다.
|
|
70
|
+
* @returns {MegaWsConnection}
|
|
71
|
+
*/
|
|
72
|
+
export function driveWsConnection({ raw, req, route, app, codec, log, auth, protocolVersion }: {
|
|
73
|
+
raw: import("ws").WebSocket;
|
|
74
|
+
req: import("node:http").IncomingMessage;
|
|
75
|
+
route: {
|
|
76
|
+
path: string;
|
|
77
|
+
ns?: string;
|
|
78
|
+
ChannelClass: Function;
|
|
79
|
+
opts: Object;
|
|
80
|
+
schemaValidators?: Record<string, import("ajv").ValidateFunction> | null;
|
|
81
|
+
};
|
|
82
|
+
app: import("./mega-app.js").MegaApp;
|
|
83
|
+
codec: WsFrameCodec;
|
|
84
|
+
log: any;
|
|
85
|
+
auth?: any;
|
|
86
|
+
protocolVersion?: number;
|
|
87
|
+
}): MegaWsConnection;
|
|
88
|
+
/**
|
|
89
|
+
* WS 메시지 메트릭 라벨용 type 을 카디널리티-안전하게 접는다(ADR-132). 채널에 **전용
|
|
90
|
+
* 핸들러 메서드가 등록된 type** 만 그대로 라벨에 노출하고, 그 외(= {@link MegaWebSocketController#dispatch}
|
|
91
|
+
* 가 `onMessage` 폴백으로 보내는 임의 type)는 {@link UNHANDLED_WS_TYPE} 로 접는다. dispatch 의 핸들러 판정
|
|
92
|
+
* (`WS_TYPE_PATTERN` + 동명 메서드 존재)을 그대로 미러하므로 "라벨로 노출되는 type = 실제 라우팅된 type" 이
|
|
93
|
+
* 보장된다. HTTP 가 매칭된 라우트 패턴만 라벨에 쓰는 것(ADR-131)과 동일한 카디널리티 정책이다.
|
|
94
|
+
*
|
|
95
|
+
* **트레이싱 span(`ws.<type>`)은 raw type 을 그대로 쓴다** — span 은 요청마다 독립이라 무한 type 도 집계
|
|
96
|
+
* 폭증을 일으키지 않지만, 메트릭 라벨은 누적 집계라 반드시 bounded 해야 하기 때문이다(둘의 의도된 차이).
|
|
97
|
+
*
|
|
98
|
+
* @param {import('./ws-controller.js').MegaWebSocketController} channel - dispatch 대상 채널 인스턴스.
|
|
99
|
+
* @param {string} type - 검증된 envelope 의 메시지 type.
|
|
100
|
+
* @returns {string} 라벨에 안전한 type(등록 핸들러 있으면 원본, 없으면 `__unhandled__`).
|
|
101
|
+
*/
|
|
102
|
+
export function wsMetricType(channel: import("./ws-controller.js").MegaWebSocketController, type: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* upgrade 핸드셰이크 거부 — 401/404 등 HTTP 응답 후 소켓 파괴.
|
|
105
|
+
* (ws 핸드셰이크 전이므로 WS close frame 이 아니라 HTTP 응답으로 거부한다.)
|
|
106
|
+
*
|
|
107
|
+
* @param {import('node:stream').Duplex} socket
|
|
108
|
+
* @param {number} status - HTTP status code.
|
|
109
|
+
* @param {string} reason - status text.
|
|
110
|
+
* @param {any} [log] - 선택 로거 (write 실패 통지용).
|
|
111
|
+
* @returns {void}
|
|
112
|
+
*/
|
|
113
|
+
export function rejectUpgrade(socket: import("node:stream").Duplex, status: number, reason: string, log?: any): void;
|
|
114
|
+
/**
|
|
115
|
+
* 라우팅 핸들러가 없는(=onMessage 폴백으로 가는) WS 메시지 type 을 접는 고정 라벨. HTTP 의
|
|
116
|
+
* `__unmatched__`(ADR-131 M-A)와 동일 정책 — 악의적 클라가 임의 type 을 무한히 보내 메트릭 라벨
|
|
117
|
+
* 카디널리티를 폭증시키는 풋건을 차단한다.
|
|
118
|
+
*/
|
|
119
|
+
export const UNHANDLED_WS_TYPE: "__unhandled__";
|
|
120
|
+
/** ASP decrypt 실패 close code (ADR-084, transport_ws.rs CLOSE_CODE_DECRYPT_FAILED 와 일치). */
|
|
121
|
+
export const CLOSE_CODE_DECRYPT_FAILED: 4500;
|
|
122
|
+
/**
|
|
123
|
+
* 서버 내부 오류 close code (RFC 6455 §7.4.1 표준 1011, M3).
|
|
124
|
+
* onConnect 실패 등 **복호화와 무관한** 서버측 오류에 사용한다 — 4500(decrypt) 과 의미 분리.
|
|
125
|
+
*/
|
|
126
|
+
export const CLOSE_CODE_INTERNAL_ERROR: 1011;
|
|
127
|
+
/** 느린 소비자 백프레셔 close code (RFC 6455 §7.4.1 표준 1013 "Try Again Later", Med). */
|
|
128
|
+
export const CLOSE_CODE_SLOW_CONSUMER: 1013;
|
|
129
|
+
/**
|
|
130
|
+
* 재배치(requeue) close code — 4503. "이 워커 말고 다른 곳으로 즉시 재연결하라"는 신호로,
|
|
131
|
+
* bridge↔hub 의 drain(4503, `hub-protocol.js` CLOSE_CODE_DRAIN)과 같은 코드를 써서 클라이언트가
|
|
132
|
+
* 한 가지 규약("4503 = 세션 유지한 채 재연결")으로 두 링크를 모두 처리한다. admin-kick 의
|
|
133
|
+
* `requeue: true`(hub.disconnect) 가 사용 — kick(1008, 돌아오지 마라)과 의미가 반대다.
|
|
134
|
+
* HTTP 세션은 보존되므로 재연결 시 `before` 인증이 세션 쿠키로 자연 통과한다(transparent re-route).
|
|
135
|
+
*/
|
|
136
|
+
export const CLOSE_CODE_REQUEUE: 4503;
|
|
137
|
+
/** send 버퍼(bufferedAmount) 기본 상한(바이트). 초과 = 소비자가 ack 를 못 따라옴 → 연결 종료(서버 OOM 방지). */
|
|
138
|
+
export const DEFAULT_MAX_BUFFERED_BYTES: number;
|
|
139
|
+
/**
|
|
140
|
+
* 프로세스 **합산** send 버퍼 기본 budget(바이트, ADR-215 — G5 audit M-6).
|
|
141
|
+
*
|
|
142
|
+
* per-conn 상한(위 16MiB)은 연결 1개의 OOM 방어일 뿐이라, 느린 소비자 N 개가 각자 cap 직전까지
|
|
143
|
+
* 쌓으면 합산은 무제한이었다(이론상 1,000개 × 16MiB = 16GB). 합산이 본 budget 을 넘으면 **가장 큰
|
|
144
|
+
* 송신 큐를 보유한 연결부터** 1013(slow consumer)으로 종료해 budget 아래로 회수한다.
|
|
145
|
+
* `configureWsSendBudget({ maxTotalBufferedBytes: 0 })` 으로 무제한 옵트아웃.
|
|
146
|
+
*/
|
|
147
|
+
export const DEFAULT_MAX_TOTAL_BUFFERED_BYTES: number;
|
|
148
|
+
/**
|
|
149
|
+
* 클라↔bridge ping/pong liveness 기본 주기(ms) — 30초. 주기마다 ping 을 보내고 직전 주기의 pong 이
|
|
150
|
+
* 없으면 half-open(상대 사망·네트워크 단절) 으로 보고 terminate 한다 — 좀비 연결이 OS TCP 타임아웃까지
|
|
151
|
+
* `_wsConns`/roster 에 잔존하는 것을 막는다. 라우트 `opts.heartbeatMs` 로 조정, `0` = 끔.
|
|
152
|
+
*/
|
|
153
|
+
export const DEFAULT_WS_HEARTBEAT_MS: 30000;
|
|
154
|
+
/**
|
|
155
|
+
* onConnect 완료 전 도착한 프레임의 대기 큐 상한 — 초과 시 연결 종료(1013). onConnect 가 느릴 때
|
|
156
|
+
* 악의적/과속 클라이언트가 큐로 메모리를 채우는 것을 막는다.
|
|
157
|
+
*/
|
|
158
|
+
export const MAX_PENDING_FRAMES_BEFORE_CONNECT: 256;
|
|
159
|
+
/**
|
|
160
|
+
* 채널 핸들러에 전달되는 소켓 래퍼 — envelope 송신을 강제한다.
|
|
161
|
+
*
|
|
162
|
+
* 사용자 채널은 `sock.send({ type, payload, ref })` 형태로 호출한다. 내부에서
|
|
163
|
+
* {@link createWsMessage} 로 v/id/ts 를 자동 채운 valid envelope 을 만든 뒤 코덱으로
|
|
164
|
+
* 인코딩해 와이어에 쓴다. raw `ws` 소켓은 {@link MegaWsConnection#raw} 로 노출 (escape hatch).
|
|
165
|
+
*/
|
|
166
|
+
export class MegaWsConnection {
|
|
167
|
+
/**
|
|
168
|
+
* @param {import('ws').WebSocket} rawSocket
|
|
169
|
+
* @param {WsFrameCodec} codec
|
|
170
|
+
* @param {{ id?: string, ns?: string, path: string, maxBufferedBytes?: number }} meta
|
|
171
|
+
*/
|
|
172
|
+
constructor(rawSocket: import("ws").WebSocket, codec: WsFrameCodec, meta: {
|
|
173
|
+
id?: string;
|
|
174
|
+
ns?: string;
|
|
175
|
+
path: string;
|
|
176
|
+
maxBufferedBytes?: number;
|
|
177
|
+
});
|
|
178
|
+
/** @type {import('ws').WebSocket} */
|
|
179
|
+
_raw: import("ws").WebSocket;
|
|
180
|
+
/** @type {number} send 버퍼 상한(바이트) — 초과 시 느린 소비자로 보고 연결 종료(백프레셔). */
|
|
181
|
+
_maxBufferedBytes: number;
|
|
182
|
+
/** @type {WsFrameCodec} */
|
|
183
|
+
_codec: WsFrameCodec;
|
|
184
|
+
/** @type {string} 연결 식별자 (ULID). */
|
|
185
|
+
id: string;
|
|
186
|
+
/** @type {string|undefined} 채널 namespace. */
|
|
187
|
+
ns: string | undefined;
|
|
188
|
+
/** @type {string} 연결 경로. */
|
|
189
|
+
path: string;
|
|
190
|
+
/** @type {string|undefined} joinSession 으로 매핑된 사용자 식별자 (DIRECT 타겟). */
|
|
191
|
+
userId: string | undefined;
|
|
192
|
+
/** @type {string|undefined} joinSession 으로 매핑된 세션 식별자. */
|
|
193
|
+
sessionId: string | undefined;
|
|
194
|
+
/** @type {Set<string>|null} joinSession 으로 가입한 채널 집합. */
|
|
195
|
+
channels: Set<string> | null;
|
|
196
|
+
/** @type {Object|undefined} joinSession/updateMetadata 로 저장한 presence 메타(재연결 재동기화에 보존, M-1). */
|
|
197
|
+
metadata: Object | undefined;
|
|
198
|
+
/** @type {number} 협상된 envelope 프로토콜 버전(기본 v1) — driveWsConnection 이 핸드셰이크 결과로 설정. */
|
|
199
|
+
protocolVersion: number;
|
|
200
|
+
/** 하위 raw `ws` WebSocket (escape hatch — 바이너리/직접 제어용). */
|
|
201
|
+
get raw(): import("ws").WebSocket;
|
|
202
|
+
/** 연결이 OPEN 상태인지 (`ws.OPEN` === 1). */
|
|
203
|
+
get isOpen(): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* envelope 송신. v/id/ts 자동 채움 (§6.2). ASP 활성 시 자동 암호화.
|
|
206
|
+
*
|
|
207
|
+
* `ns` 가 생략되면 연결의 namespace(`this.ns`)를 자동 주입한다(L1). 호출부가 명시한 `ns` 는
|
|
208
|
+
* 그대로 존중한다(다른 namespace 로 보내는 경우 등).
|
|
209
|
+
*
|
|
210
|
+
* @param {{ type: string, ns?: string, payload?: Object, error?: Object, ref?: string }} fields
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
send(fields: {
|
|
214
|
+
type: string;
|
|
215
|
+
ns?: string;
|
|
216
|
+
payload?: Object;
|
|
217
|
+
error?: Object;
|
|
218
|
+
ref?: string;
|
|
219
|
+
}): void;
|
|
220
|
+
/**
|
|
221
|
+
* error envelope 송신 (§6.3). 핸들러 throw / 검증 실패 통지용.
|
|
222
|
+
* @param {{ code: string, message?: string, details?: unknown, ref?: string, type?: string }} opts
|
|
223
|
+
* @returns {void}
|
|
224
|
+
*/
|
|
225
|
+
sendError({ code, message, details, ref, type }: {
|
|
226
|
+
code: string;
|
|
227
|
+
message?: string;
|
|
228
|
+
details?: unknown;
|
|
229
|
+
ref?: string;
|
|
230
|
+
type?: string;
|
|
231
|
+
}): void;
|
|
232
|
+
/**
|
|
233
|
+
* 연결 종료.
|
|
234
|
+
* @param {number} [code]
|
|
235
|
+
* @param {string} [reason]
|
|
236
|
+
* @returns {void}
|
|
237
|
+
*/
|
|
238
|
+
close(code?: number, reason?: string): void;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* WS 프레임 코덱 — 평문/암호 와이어 변환을 추상화한다.
|
|
242
|
+
*/
|
|
243
|
+
export type WsFrameCodec = {
|
|
244
|
+
/**
|
|
245
|
+
* - 수신 프레임 → 평문 JSON 문자열 (실패 시 throw).
|
|
246
|
+
*/
|
|
247
|
+
decode: (frame: string) => string;
|
|
248
|
+
/**
|
|
249
|
+
* - 평문 JSON → 송신 프레임 (기본: ASP 활성 시 암호).
|
|
250
|
+
*/
|
|
251
|
+
encode: (json: string) => string;
|
|
252
|
+
/**
|
|
253
|
+
* - 평문 JSON → 평문 송신 프레임 (asp.error 통지용).
|
|
254
|
+
*/
|
|
255
|
+
encodePlain: (json: string) => string;
|
|
256
|
+
/**
|
|
257
|
+
* - 기본 송신이 암호화인지.
|
|
258
|
+
*/
|
|
259
|
+
isEncrypted: boolean;
|
|
260
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 부팅 시 config 검증 실패 (3-스코프, 키 오타, 호스트 충돌, Shared-Reference 등).
|
|
3
|
+
* code = 'config.<reason>'.
|
|
4
|
+
*
|
|
5
|
+
* 부팅 시 throw 되어 프로세스를 중단시킨다 (fail-fast). silent catch 금지 —
|
|
6
|
+
* 이 에러를 잡아서 무시하면 잘못된 config 로 서버가 뜨므로 절대 삼키지 말 것.
|
|
7
|
+
*/
|
|
8
|
+
export class MegaConfigError extends MegaError {
|
|
9
|
+
}
|
|
10
|
+
import { MegaError } from './mega-error.js';
|