@simplysm/service-server 13.0.100 → 14.0.4
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 +79 -133
- package/dist/auth/auth-token-payload.js +2 -1
- package/dist/auth/auth-token-payload.js.map +1 -6
- package/dist/auth/jwt-manager.js +21 -21
- package/dist/auth/jwt-manager.js.map +1 -6
- package/dist/core/define-service.d.ts +12 -12
- package/dist/core/define-service.d.ts.map +1 -1
- package/dist/core/define-service.js +77 -63
- package/dist/core/define-service.js.map +1 -6
- package/dist/core/service-executor.d.ts.map +1 -1
- package/dist/core/service-executor.js +42 -32
- package/dist/core/service-executor.js.map +1 -6
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -6
- package/dist/legacy/v1-auto-update-handler.d.ts +2 -2
- package/dist/legacy/v1-auto-update-handler.js +42 -35
- package/dist/legacy/v1-auto-update-handler.js.map +1 -6
- package/dist/protocol/protocol-wrapper.d.ts +9 -9
- package/dist/protocol/protocol-wrapper.js +64 -46
- package/dist/protocol/protocol-wrapper.js.map +1 -6
- package/dist/service-server.d.ts +2 -1
- package/dist/service-server.d.ts.map +1 -1
- package/dist/service-server.js +183 -165
- package/dist/service-server.js.map +1 -6
- package/dist/services/auto-update-service.js +35 -34
- package/dist/services/auto-update-service.js.map +1 -6
- package/dist/services/orm-service.js +114 -120
- package/dist/services/orm-service.js.map +1 -6
- package/dist/transport/http/http-request-handler.d.ts.map +1 -1
- package/dist/transport/http/http-request-handler.js +58 -46
- package/dist/transport/http/http-request-handler.js.map +1 -6
- package/dist/transport/http/static-file-handler.js +42 -39
- package/dist/transport/http/static-file-handler.js.map +1 -6
- package/dist/transport/http/upload-handler.d.ts.map +1 -1
- package/dist/transport/http/upload-handler.js +60 -55
- package/dist/transport/http/upload-handler.js.map +1 -6
- package/dist/transport/socket/service-socket.d.ts +13 -13
- package/dist/transport/socket/service-socket.js +132 -108
- package/dist/transport/socket/service-socket.js.map +1 -6
- package/dist/transport/socket/websocket-handler.d.ts +9 -13
- package/dist/transport/socket/websocket-handler.d.ts.map +1 -1
- package/dist/transport/socket/websocket-handler.js +148 -139
- package/dist/transport/socket/websocket-handler.js.map +1 -6
- package/dist/types/server-options.d.ts +1 -1
- package/dist/types/server-options.d.ts.map +1 -1
- package/dist/types/server-options.js +2 -1
- package/dist/types/server-options.js.map +1 -6
- package/dist/utils/config-manager.js +48 -46
- package/dist/utils/config-manager.js.map +1 -6
- package/dist/workers/service-protocol.worker.js +8 -11
- package/dist/workers/service-protocol.worker.js.map +1 -6
- package/docs/auth.md +28 -16
- package/docs/core.md +113 -54
- package/docs/legacy.md +21 -0
- package/docs/main.md +81 -0
- package/docs/protocol.md +31 -0
- package/docs/services.md +49 -44
- package/docs/transport.md +81 -76
- package/docs/types.md +31 -0
- package/docs/utilities.md +27 -0
- package/package.json +12 -13
- package/src/auth/jwt-manager.ts +2 -2
- package/src/core/define-service.ts +19 -19
- package/src/core/service-executor.ts +23 -17
- package/src/index.ts +10 -12
- package/src/legacy/v1-auto-update-handler.ts +10 -10
- package/src/protocol/protocol-wrapper.ts +16 -16
- package/src/service-server.ts +51 -43
- package/src/services/auto-update-service.ts +1 -1
- package/src/services/orm-service.ts +7 -7
- package/src/transport/http/http-request-handler.ts +16 -10
- package/src/transport/http/static-file-handler.ts +8 -8
- package/src/transport/http/upload-handler.ts +16 -9
- package/src/transport/socket/service-socket.ts +22 -22
- package/src/transport/socket/websocket-handler.ts +50 -70
- package/src/types/server-options.ts +1 -1
- package/src/utils/config-manager.ts +11 -11
- package/dist/services/smtp-client-service.d.ts +0 -8
- package/dist/services/smtp-client-service.d.ts.map +0 -1
- package/dist/services/smtp-client-service.js +0 -46
- package/dist/services/smtp-client-service.js.map +0 -6
- package/docs/server.md +0 -126
- package/src/services/smtp-client-service.ts +0 -59
- package/tests/define-service.spec.ts +0 -66
- package/tests/orm-service.spec.ts +0 -83
- package/tests/service-executor.spec.ts +0 -114
package/docs/transport.md
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
1
|
# Transport
|
|
2
2
|
|
|
3
|
+
WebSocket and HTTP transport handlers for routing client messages to services.
|
|
4
|
+
|
|
3
5
|
## WebSocket Transport
|
|
4
6
|
|
|
5
|
-
###
|
|
7
|
+
### WebSocketHandler
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Manages multiple WebSocket connections, routes messages to services, and handles event broadcasting.
|
|
8
10
|
|
|
9
|
-
```
|
|
11
|
+
```ts
|
|
10
12
|
interface WebSocketHandler {
|
|
11
13
|
addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
|
|
12
14
|
closeAll(): void;
|
|
13
|
-
|
|
14
|
-
emit<TInfo, TData>(
|
|
15
|
-
eventDef: ServiceEventDef<TInfo, TData>,
|
|
16
|
-
infoSelector: (item: TInfo) => boolean,
|
|
17
|
-
data: TData,
|
|
18
|
-
): Promise<void>;
|
|
15
|
+
emit<TInfo, TData>(eventDef: ServiceEventDef<TInfo, TData>, infoSelector: (item: TInfo) => boolean, data: TData): Promise<void>;
|
|
19
16
|
}
|
|
20
17
|
```
|
|
21
18
|
|
|
22
|
-
| Method | Description |
|
|
23
|
-
|
|
24
|
-
| `addSocket
|
|
25
|
-
| `closeAll
|
|
26
|
-
| `
|
|
27
|
-
| `emit()` | Emit event to matching clients |
|
|
19
|
+
| Method | Parameters | Return | Description |
|
|
20
|
+
|--------|-----------|--------|-------------|
|
|
21
|
+
| `addSocket` | `socket: WebSocket`, `clientId: string`, `clientName: string`, `connReq: FastifyRequest` | `void` | Register a new WebSocket connection |
|
|
22
|
+
| `closeAll` | -- | `void` | Close all managed connections |
|
|
23
|
+
| `emit` | `eventDef`, `infoSelector`, `data` | `Promise<void>` | Broadcast an event to matching listeners across all sockets |
|
|
28
24
|
|
|
29
|
-
###
|
|
25
|
+
### createWebSocketHandler
|
|
30
26
|
|
|
31
|
-
Create a
|
|
27
|
+
Create a `WebSocketHandler` instance.
|
|
32
28
|
|
|
33
|
-
```
|
|
29
|
+
```ts
|
|
34
30
|
function createWebSocketHandler(
|
|
35
31
|
runMethod: (def: {
|
|
36
32
|
serviceName: string;
|
|
37
33
|
methodName: string;
|
|
38
34
|
params: unknown[];
|
|
39
|
-
socket
|
|
35
|
+
socket?: ServiceSocket;
|
|
40
36
|
}) => Promise<unknown>,
|
|
41
|
-
jwtSecret
|
|
37
|
+
jwtSecret: string | undefined,
|
|
42
38
|
): WebSocketHandler;
|
|
43
39
|
```
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
| Parameter | Type | Description |
|
|
42
|
+
|-----------|------|-------------|
|
|
43
|
+
| `runMethod` | `(def) => Promise<unknown>` | Callback to execute a service method |
|
|
44
|
+
| `jwtSecret` | `string \| undefined` | JWT secret for authenticating socket connections. `undefined` disables auth. |
|
|
45
|
+
|
|
46
|
+
### ServiceSocket
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
Represents a single WebSocket connection with protocol encoding/decoding, ping/pong keep-alive, and event listener tracking.
|
|
48
49
|
|
|
49
|
-
```
|
|
50
|
+
```ts
|
|
50
51
|
interface ServiceSocket {
|
|
51
52
|
readonly connectedAtDateTime: DateTime;
|
|
52
53
|
readonly clientName: string;
|
|
53
54
|
readonly connReq: FastifyRequest;
|
|
54
55
|
authTokenPayload?: AuthTokenPayload;
|
|
55
|
-
|
|
56
56
|
close(): void;
|
|
57
57
|
send(uuid: string, msg: ServiceServerMessage): Promise<number>;
|
|
58
58
|
addListener(key: string, eventName: string, info: unknown): void;
|
|
@@ -65,28 +65,27 @@ interface ServiceSocket {
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
| `connectedAtDateTime` | `DateTime` | Connection
|
|
71
|
-
| `clientName` | `string` | Client
|
|
72
|
-
| `connReq` | `FastifyRequest` | Original Fastify request |
|
|
73
|
-
| `authTokenPayload` | `AuthTokenPayload
|
|
74
|
-
|
|
75
|
-
|
|
|
76
|
-
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
68
|
+
| Member | Kind | Type | Description |
|
|
69
|
+
|--------|------|------|-------------|
|
|
70
|
+
| `connectedAtDateTime` | property | `DateTime` | Connection timestamp |
|
|
71
|
+
| `clientName` | property | `string` | Client identifier |
|
|
72
|
+
| `connReq` | property | `FastifyRequest` | Original Fastify request |
|
|
73
|
+
| `authTokenPayload` | property | `AuthTokenPayload?` | Authenticated token payload (set after auth message) |
|
|
74
|
+
| `close` | method | `() => void` | Close the socket |
|
|
75
|
+
| `send` | method | `(uuid, msg) => Promise<number>` | Send a server message; returns number of bytes sent |
|
|
76
|
+
| `addListener` | method | `(key, eventName, info) => void` | Register an event listener on this socket |
|
|
77
|
+
| `removeListener` | method | `(key) => void` | Remove an event listener by key |
|
|
78
|
+
| `getEventListeners` | method | `(eventName) => Array<{ key; info }>` | Get all listeners for an event name |
|
|
79
|
+
| `filterEventTargetKeys` | method | `(targetKeys) => string[]` | Filter target keys to only those on this socket |
|
|
80
|
+
| `on("error")` | method | `(handler) => void` | Subscribe to error events |
|
|
81
|
+
| `on("close")` | method | `(handler) => void` | Subscribe to close events (with close code) |
|
|
82
|
+
| `on("message")` | method | `(handler) => void` | Subscribe to decoded message events |
|
|
83
|
+
|
|
84
|
+
### createServiceSocket
|
|
85
|
+
|
|
86
|
+
Create a `ServiceSocket` instance wrapping a raw WebSocket.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
90
89
|
function createServiceSocket(
|
|
91
90
|
socket: WebSocket,
|
|
92
91
|
clientId: string,
|
|
@@ -95,13 +94,20 @@ function createServiceSocket(
|
|
|
95
94
|
): ServiceSocket;
|
|
96
95
|
```
|
|
97
96
|
|
|
97
|
+
| Parameter | Type | Description |
|
|
98
|
+
|-----------|------|-------------|
|
|
99
|
+
| `socket` | `WebSocket` | Raw WebSocket instance |
|
|
100
|
+
| `clientId` | `string` | Unique connection identifier |
|
|
101
|
+
| `clientName` | `string` | Client name |
|
|
102
|
+
| `connReq` | `FastifyRequest` | Fastify request that initiated the upgrade |
|
|
103
|
+
|
|
98
104
|
## HTTP Transport
|
|
99
105
|
|
|
100
|
-
###
|
|
106
|
+
### handleHttpRequest
|
|
101
107
|
|
|
102
|
-
Handle HTTP
|
|
108
|
+
Handle an HTTP RPC request. Parses the request body, verifies authentication, executes the service method, and sends the response.
|
|
103
109
|
|
|
104
|
-
```
|
|
110
|
+
```ts
|
|
105
111
|
async function handleHttpRequest<TAuthInfo = unknown>(
|
|
106
112
|
req: FastifyRequest,
|
|
107
113
|
reply: FastifyReply,
|
|
@@ -115,11 +121,18 @@ async function handleHttpRequest<TAuthInfo = unknown>(
|
|
|
115
121
|
): Promise<void>;
|
|
116
122
|
```
|
|
117
123
|
|
|
118
|
-
|
|
124
|
+
| Parameter | Type | Description |
|
|
125
|
+
|-----------|------|-------------|
|
|
126
|
+
| `req` | `FastifyRequest` | Fastify request |
|
|
127
|
+
| `reply` | `FastifyReply` | Fastify reply |
|
|
128
|
+
| `jwtSecret` | `string \| undefined` | JWT secret. `undefined` disables auth verification. |
|
|
129
|
+
| `runMethod` | `(def) => Promise<unknown>` | Callback to execute the service method |
|
|
119
130
|
|
|
120
|
-
|
|
131
|
+
### handleUpload
|
|
121
132
|
|
|
122
|
-
|
|
133
|
+
Handle a file upload request. Saves uploaded files to the server's root path and returns upload results.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
123
136
|
async function handleUpload(
|
|
124
137
|
req: FastifyRequest,
|
|
125
138
|
reply: FastifyReply,
|
|
@@ -128,11 +141,18 @@ async function handleUpload(
|
|
|
128
141
|
): Promise<void>;
|
|
129
142
|
```
|
|
130
143
|
|
|
131
|
-
|
|
144
|
+
| Parameter | Type | Description |
|
|
145
|
+
|-----------|------|-------------|
|
|
146
|
+
| `req` | `FastifyRequest` | Fastify request (multipart) |
|
|
147
|
+
| `reply` | `FastifyReply` | Fastify reply |
|
|
148
|
+
| `rootPath` | `string` | Server root path for file storage |
|
|
149
|
+
| `jwtSecret` | `string \| undefined` | JWT secret for auth verification |
|
|
150
|
+
|
|
151
|
+
### handleStaticFile
|
|
132
152
|
|
|
133
|
-
|
|
153
|
+
Serve a static file from the server's root path.
|
|
134
154
|
|
|
135
|
-
```
|
|
155
|
+
```ts
|
|
136
156
|
async function handleStaticFile(
|
|
137
157
|
req: FastifyRequest,
|
|
138
158
|
reply: FastifyReply,
|
|
@@ -141,24 +161,9 @@ async function handleStaticFile(
|
|
|
141
161
|
): Promise<void>;
|
|
142
162
|
```
|
|
143
163
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
interface ServerProtocolWrapper {
|
|
152
|
-
encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
|
|
153
|
-
decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
|
|
154
|
-
dispose(): void;
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### `createServerProtocolWrapper`
|
|
159
|
-
|
|
160
|
-
Create a server protocol wrapper instance.
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
function createServerProtocolWrapper(): ServerProtocolWrapper;
|
|
164
|
-
```
|
|
164
|
+
| Parameter | Type | Description |
|
|
165
|
+
|-----------|------|-------------|
|
|
166
|
+
| `req` | `FastifyRequest` | Fastify request |
|
|
167
|
+
| `reply` | `FastifyReply` | Fastify reply |
|
|
168
|
+
| `rootPath` | `string` | Server root directory |
|
|
169
|
+
| `urlPath` | `string` | URL path portion to resolve |
|
package/docs/types.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Types
|
|
2
|
+
|
|
3
|
+
## ServiceServerOptions
|
|
4
|
+
|
|
5
|
+
Server configuration options passed to `createServiceServer`.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
interface ServiceServerOptions {
|
|
9
|
+
rootPath: string;
|
|
10
|
+
port: number;
|
|
11
|
+
ssl?: {
|
|
12
|
+
pfxBytes: Uint8Array;
|
|
13
|
+
passphrase: string;
|
|
14
|
+
};
|
|
15
|
+
auth?: {
|
|
16
|
+
jwtSecret: string;
|
|
17
|
+
} | false;
|
|
18
|
+
services: ServiceDefinition[];
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Field | Type | Description |
|
|
23
|
+
|-------|------|-------------|
|
|
24
|
+
| `rootPath` | `string` | Root directory path for static files and uploads |
|
|
25
|
+
| `port` | `number` | Server listening port |
|
|
26
|
+
| `ssl` | `{ pfxBytes: Uint8Array; passphrase: string }?` | SSL/TLS configuration using a PFX certificate |
|
|
27
|
+
| `ssl.pfxBytes` | `Uint8Array` | PFX certificate bytes |
|
|
28
|
+
| `ssl.passphrase` | `string` | PFX passphrase |
|
|
29
|
+
| `auth` | `{ jwtSecret: string } \| false` | JWT authentication configuration. Set to `false` to disable authentication. |
|
|
30
|
+
| `auth.jwtSecret` | `string` | Secret key for JWT signing and verification |
|
|
31
|
+
| `services` | `ServiceDefinition[]` | Array of service definitions to register |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Utilities
|
|
2
|
+
|
|
3
|
+
## getConfig
|
|
4
|
+
|
|
5
|
+
Read a configuration section from a JSON file.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>;
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Parameter | Type | Description |
|
|
12
|
+
|-----------|------|-------------|
|
|
13
|
+
| `filePath` | `string` | Path to the configuration JSON file |
|
|
14
|
+
|
|
15
|
+
Returns the parsed configuration object typed as `TConfig`, or `undefined` if the file does not exist.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { getConfig } from "@simplysm/service-server";
|
|
19
|
+
|
|
20
|
+
interface DbConfig {
|
|
21
|
+
host: string;
|
|
22
|
+
port: number;
|
|
23
|
+
database: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dbConfig = await getConfig<DbConfig>("config/db.json");
|
|
27
|
+
```
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "14.0.4",
|
|
4
|
+
"description": "심플리즘 패키지 - 서비스 (server)",
|
|
5
|
+
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
-
"docs",
|
|
18
17
|
"src",
|
|
19
|
-
"
|
|
18
|
+
"docs"
|
|
20
19
|
],
|
|
21
20
|
"sideEffects": false,
|
|
22
21
|
"dependencies": {
|
|
@@ -29,18 +28,18 @@
|
|
|
29
28
|
"@fastify/websocket": "^11.2.0",
|
|
30
29
|
"bufferutil": "^4.1.0",
|
|
31
30
|
"consola": "^3.4.2",
|
|
32
|
-
"fastify": "^5.8.
|
|
31
|
+
"fastify": "^5.8.4",
|
|
33
32
|
"jose": "^6.2.2",
|
|
34
33
|
"mime": "^4.1.0",
|
|
35
|
-
"nodemailer": "^8.0.
|
|
34
|
+
"nodemailer": "^8.0.4",
|
|
36
35
|
"semver": "^7.7.4",
|
|
37
36
|
"utf-8-validate": "^6.0.6",
|
|
38
|
-
"ws": "^8.
|
|
39
|
-
"@simplysm/core-common": "
|
|
40
|
-
"@simplysm/
|
|
41
|
-
"@simplysm/orm-
|
|
42
|
-
"@simplysm/service-common": "
|
|
43
|
-
"@simplysm/
|
|
37
|
+
"ws": "^8.20.0",
|
|
38
|
+
"@simplysm/core-common": "14.0.4",
|
|
39
|
+
"@simplysm/orm-common": "14.0.4",
|
|
40
|
+
"@simplysm/orm-node": "14.0.4",
|
|
41
|
+
"@simplysm/service-common": "14.0.4",
|
|
42
|
+
"@simplysm/core-node": "14.0.4"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
45
|
"@types/nodemailer": "^7.0.11",
|
package/src/auth/jwt-manager.ts
CHANGED
|
@@ -25,9 +25,9 @@ export async function verifyJwt<TAuthInfo = unknown>(
|
|
|
25
25
|
return payload as AuthTokenPayload<TAuthInfo>;
|
|
26
26
|
} catch (err) {
|
|
27
27
|
if (err != null && typeof err === "object" && "code" in err && err.code === "ERR_JWT_EXPIRED") {
|
|
28
|
-
throw new Error("
|
|
28
|
+
throw new Error("토큰이 만료되었습니다.");
|
|
29
29
|
}
|
|
30
|
-
throw new Error("
|
|
30
|
+
throw new Error("유효하지 않은 토큰입니다.");
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -5,7 +5,7 @@ import { obj } from "@simplysm/core-common";
|
|
|
5
5
|
import { getConfig } from "../utils/config-manager";
|
|
6
6
|
import path from "path";
|
|
7
7
|
|
|
8
|
-
// ──
|
|
8
|
+
// ── 컨텍스트 ──
|
|
9
9
|
|
|
10
10
|
export interface ServiceContext<TAuthInfo = unknown> {
|
|
11
11
|
server: ServiceServer<TAuthInfo>;
|
|
@@ -15,7 +15,7 @@ export interface ServiceContext<TAuthInfo = unknown> {
|
|
|
15
15
|
authTokenPayload?: AuthTokenPayload<TAuthInfo>;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
/** V1
|
|
18
|
+
/** V1 레거시 컨텍스트 (자동 업데이트 전용) */
|
|
19
19
|
legacy?: {
|
|
20
20
|
clientName?: string;
|
|
21
21
|
};
|
|
@@ -49,7 +49,7 @@ export function createServiceContext<TAuthInfo = unknown>(
|
|
|
49
49
|
if (name == null) return undefined;
|
|
50
50
|
|
|
51
51
|
if (name === "" || name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
52
|
-
throw new Error(
|
|
52
|
+
throw new Error(`유효하지 않은 클라이언트 이름: ${name}`);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
return name;
|
|
@@ -79,28 +79,28 @@ export function createServiceContext<TAuthInfo = unknown>(
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const config = configParent[section];
|
|
82
|
-
if (config == null) throw new Error(
|
|
82
|
+
if (config == null) throw new Error(`설정 섹션을 찾을 수 없습니다: ${section}`);
|
|
83
83
|
return config;
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// ──
|
|
88
|
+
// ── 인증 ──
|
|
89
89
|
|
|
90
90
|
const AUTH_PERMISSIONS = Symbol("authPermissions");
|
|
91
91
|
|
|
92
|
-
/**
|
|
92
|
+
/** auth()로 래핑된 함수에서 인증 권한을 읽는다. 래핑되지 않은 경우 undefined를 반환한다. */
|
|
93
93
|
export function getServiceAuthPermissions(fn: Function): string[] | undefined {
|
|
94
94
|
return (fn as unknown as Record<symbol, unknown>)[AUTH_PERMISSIONS] as string[] | undefined;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* 서비스 팩토리 및 메서드용 인증 래퍼.
|
|
99
99
|
*
|
|
100
|
-
* -
|
|
101
|
-
* -
|
|
102
|
-
* -
|
|
103
|
-
* -
|
|
100
|
+
* - 서비스 수준: `auth((ctx) => ({ ... }))` — 모든 메서드에 로그인 필요
|
|
101
|
+
* - 서비스 수준 (역할 지정): `auth(["admin"], (ctx) => ({ ... }))`
|
|
102
|
+
* - 메서드 수준: `auth(() => result)` — 해당 메서드에 로그인 필요
|
|
103
|
+
* - 메서드 수준 (역할 지정): `auth(["admin"], () => result)`
|
|
104
104
|
*/
|
|
105
105
|
export function auth<TFunction extends (...args: any[]) => any>(fn: TFunction): TFunction;
|
|
106
106
|
export function auth<TFunction extends (...args: any[]) => any>(
|
|
@@ -111,14 +111,14 @@ export function auth(permissionsOrFn: string[] | Function, maybeFn?: Function):
|
|
|
111
111
|
const permissions = Array.isArray(permissionsOrFn) ? permissionsOrFn : [];
|
|
112
112
|
const fn = Array.isArray(permissionsOrFn) ? maybeFn! : permissionsOrFn;
|
|
113
113
|
|
|
114
|
-
//
|
|
114
|
+
// 호출 동작을 유지하는 래퍼 생성
|
|
115
115
|
const wrapper = (...args: unknown[]) => fn(...args);
|
|
116
116
|
(wrapper as unknown as Record<symbol, unknown>)[AUTH_PERMISSIONS] = permissions;
|
|
117
117
|
|
|
118
118
|
return wrapper;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// ──
|
|
121
|
+
// ── 서비스 정의 ──
|
|
122
122
|
|
|
123
123
|
export interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>> {
|
|
124
124
|
name: string;
|
|
@@ -127,15 +127,15 @@ export interface ServiceDefinition<TMethods = Record<string, (...args: any[]) =>
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
130
|
+
* 이름과 팩토리 함수로 서비스를 정의한다.
|
|
131
131
|
*
|
|
132
132
|
* @example
|
|
133
|
-
* //
|
|
133
|
+
* // 기본 서비스
|
|
134
134
|
* const HealthService = defineService("Health", (ctx) => ({
|
|
135
135
|
* check: () => ({ status: "ok" }),
|
|
136
136
|
* }));
|
|
137
137
|
*
|
|
138
|
-
* //
|
|
138
|
+
* // 인증이 필요한 서비스
|
|
139
139
|
* const UserService = defineService("User", auth((ctx) => ({
|
|
140
140
|
* getProfile: () => ctx.authInfo,
|
|
141
141
|
* adminOnly: auth(["admin"], () => "admin"),
|
|
@@ -152,14 +152,14 @@ export function defineService<TMethods extends Record<string, (...args: any[]) =
|
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// ──
|
|
155
|
+
// ── 타입 유틸리티 ──
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
*
|
|
158
|
+
* 클라이언트 측 타입 공유를 위해 ServiceDefinition에서 메서드 시그니처를 추출한다.
|
|
159
159
|
*
|
|
160
160
|
* @example
|
|
161
161
|
* export type UserServiceType = ServiceMethods<typeof UserService>;
|
|
162
|
-
* //
|
|
162
|
+
* // 클라이언트: client.getService<UserServiceType>("User");
|
|
163
163
|
*/
|
|
164
164
|
export type ServiceMethods<TDefinition> =
|
|
165
165
|
TDefinition extends ServiceDefinition<infer M> ? M : never;
|
|
@@ -13,55 +13,61 @@ export async function executeServiceMethod(
|
|
|
13
13
|
http?: { clientName: string; authTokenPayload?: AuthTokenPayload };
|
|
14
14
|
},
|
|
15
15
|
): Promise<unknown> {
|
|
16
|
-
//
|
|
16
|
+
// 서비스 정의 검색
|
|
17
17
|
const serviceDef = server.options.services.find((item) => item.name === def.serviceName);
|
|
18
18
|
|
|
19
19
|
if (serviceDef == null) {
|
|
20
|
-
throw new Error(
|
|
20
|
+
throw new Error(`서비스 [${def.serviceName}]를 찾을 수 없습니다.`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// 요청 유효성 검증 (게이트키퍼)
|
|
24
24
|
const clientName = def.socket?.clientName ?? def.http?.clientName;
|
|
25
25
|
if (clientName != null) {
|
|
26
26
|
if (clientName.includes("..") || clientName.includes("/") || clientName.includes("\\")) {
|
|
27
|
-
throw new Error(`[
|
|
27
|
+
throw new Error(`[보안] 유효하지 않은 클라이언트 이름: ${clientName}`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// 컨텍스트 생성
|
|
32
32
|
const ctx = createServiceContext(server, def.socket, def.http);
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// 팩토리를 호출하여 메서드 객체 생성
|
|
35
35
|
const methods = serviceDef.factory(ctx);
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// 메서드 검색
|
|
38
38
|
const method = (methods as Record<string, unknown>)[def.methodName];
|
|
39
39
|
if (typeof method !== "function") {
|
|
40
|
-
throw new Error(
|
|
40
|
+
throw new Error(`메서드 [${def.serviceName}.${def.methodName}]를 찾을 수 없습니다.`);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const methodPerms = getServiceAuthPermissions(method);
|
|
47
|
-
const requiredPerms = methodPerms ?? serviceDef.authPermissions;
|
|
43
|
+
// 인증 확인
|
|
44
|
+
const methodPerms = getServiceAuthPermissions(method);
|
|
45
|
+
const requiredPerms = methodPerms ?? serviceDef.authPermissions;
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
if (requiredPerms != null) {
|
|
48
|
+
if (server.options.auth === undefined) {
|
|
49
|
+
// auth 설정 누락 — 설정 오류
|
|
50
|
+
throw new Error("auth 설정이 필요합니다. auth 서비스를 사용하려면 서버 옵션에 auth를 설정하세요.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (server.options.auth !== false) {
|
|
54
|
+
// auth가 설정되어 있으면 인증 검사 수행
|
|
50
55
|
const authTokenPayload = def.socket?.authTokenPayload ?? def.http?.authTokenPayload;
|
|
51
56
|
|
|
52
57
|
if (authTokenPayload == null) {
|
|
53
|
-
throw new Error("
|
|
58
|
+
throw new Error("로그인이 필요합니다.");
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
if (requiredPerms.length > 0) {
|
|
57
62
|
const hasPerm = requiredPerms.some((perm) => authTokenPayload.roles.includes(perm));
|
|
58
63
|
if (!hasPerm) {
|
|
59
|
-
throw new Error("
|
|
64
|
+
throw new Error("권한이 부족합니다.");
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
68
|
+
// auth === false → 의도적 비활성화, 인증 스킵
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
//
|
|
71
|
+
// 실행
|
|
66
72
|
return await method(...def.params);
|
|
67
73
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 타입
|
|
2
2
|
export * from "./types/server-options";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// 인증
|
|
5
5
|
export * from "./auth/auth-token-payload";
|
|
6
6
|
export * from "./auth/jwt-manager";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// 코어
|
|
9
9
|
export * from "./core/define-service";
|
|
10
10
|
export * from "./core/service-executor";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// 전송 계층 - Socket
|
|
13
13
|
export * from "./transport/socket/websocket-handler";
|
|
14
14
|
export * from "./transport/socket/service-socket";
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// 전송 계층 - HTTP
|
|
17
17
|
export * from "./transport/http/http-request-handler";
|
|
18
18
|
export * from "./transport/http/upload-handler";
|
|
19
19
|
export * from "./transport/http/static-file-handler";
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// 프로토콜
|
|
22
22
|
export * from "./protocol/protocol-wrapper";
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// 서비스
|
|
25
25
|
export * from "./services/orm-service";
|
|
26
26
|
export * from "./services/auto-update-service";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// Utils
|
|
27
|
+
// 유틸리티
|
|
30
28
|
export * from "./utils/config-manager";
|
|
31
29
|
|
|
32
|
-
//
|
|
30
|
+
// 레거시
|
|
33
31
|
export * from "./legacy/v1-auto-update-handler";
|
|
34
32
|
|
|
35
|
-
//
|
|
33
|
+
// 메인
|
|
36
34
|
export * from "./service-server";
|
|
@@ -18,27 +18,27 @@ interface IV1Response {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* V1
|
|
22
|
-
*
|
|
21
|
+
* V1 레거시 클라이언트 핸들러 (자동 업데이트만 지원).
|
|
22
|
+
* 그 외 모든 요청은 업그레이드 필요 에러를 반환한다.
|
|
23
23
|
*/
|
|
24
24
|
export function handleV1Connection(
|
|
25
25
|
socket: WebSocket,
|
|
26
26
|
autoUpdateMethods: { getLastVersion: (platform: string) => Promise<any> },
|
|
27
27
|
clientNameSetter?: (clientName: string | undefined) => void,
|
|
28
28
|
) {
|
|
29
|
-
//
|
|
29
|
+
// 연결 성립 알림
|
|
30
30
|
socket.send(JSON.stringify({ name: "connected" }));
|
|
31
31
|
|
|
32
|
-
socket.on("message", (data) => {
|
|
32
|
+
socket.on("message", async (data) => {
|
|
33
33
|
try {
|
|
34
34
|
const msg = JSON.parse(data.toString()) as IV1Request;
|
|
35
35
|
|
|
36
|
-
//
|
|
36
|
+
// SdAutoUpdateService.getLastVersion만 허용
|
|
37
37
|
if (msg.command === "SdAutoUpdateService.getLastVersion") {
|
|
38
|
-
//
|
|
38
|
+
// 레거시 컨텍스트 설정
|
|
39
39
|
clientNameSetter?.(msg.clientName);
|
|
40
40
|
|
|
41
|
-
const result = autoUpdateMethods.getLastVersion(msg.params[0] as string);
|
|
41
|
+
const result = await autoUpdateMethods.getLastVersion(msg.params[0] as string);
|
|
42
42
|
|
|
43
43
|
const response: IV1Response = {
|
|
44
44
|
name: "response",
|
|
@@ -48,20 +48,20 @@ export function handleV1Connection(
|
|
|
48
48
|
};
|
|
49
49
|
socket.send(JSON.stringify(response));
|
|
50
50
|
} else {
|
|
51
|
-
//
|
|
51
|
+
// 그 외 모든 요청은 업그레이드 요구
|
|
52
52
|
const response: IV1Response = {
|
|
53
53
|
name: "response",
|
|
54
54
|
reqUuid: msg.uuid,
|
|
55
55
|
state: "error",
|
|
56
56
|
body: {
|
|
57
|
-
message: "
|
|
57
|
+
message: "앱 업그레이드가 필요합니다.",
|
|
58
58
|
code: "UPGRADE_REQUIRED",
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
socket.send(JSON.stringify(response));
|
|
62
62
|
}
|
|
63
63
|
} catch (err) {
|
|
64
|
-
logger.warn("V1
|
|
64
|
+
logger.warn("V1 메시지 처리 에러", err);
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
}
|